Android Room - simple select query - Cannot access database on the main thread
Asked Answered
B

24

233

I am trying a sample with Room Persistence Library. I created an Entity:

@Entity
public class Agent {
    @PrimaryKey
    public String guid;
    public String name;
    public String email;
    public String password;
    public String phone;
    public String licence;
}

Created a DAO class:

@Dao
public interface AgentDao {
    @Query("SELECT COUNT(*) FROM Agent where email = :email OR phone = :phone OR licence = :licence")
    int agentsCount(String email, String phone, String licence);

    @Insert
    void insertAgent(Agent agent);
}

Created the Database class:

@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract AgentDao agentDao();
}

Exposed database using below subclass in Kotlin:

class MyApp : Application() {

    companion object DatabaseSetup {
        var database: AppDatabase? = null
    }

    override fun onCreate() {
        super.onCreate()
        MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").build()
    }
}

Implemented below function in my activity:

void signUpAction(View view) {
        String email = editTextEmail.getText().toString();
        String phone = editTextPhone.getText().toString();
        String license = editTextLicence.getText().toString();

        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        //1: Check if agent already exists
        int agentsCount = agentDao.agentsCount(email, phone, license);
        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(this, "Agent already exists!", Toast.LENGTH_LONG).show();
        }
        else {
            Toast.makeText(this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            onBackPressed();
        }
    }

Unfortunately on execution of above method it crashes with below stack trace:

    FATAL EXCEPTION: main
 Process: com.example.me.MyApp, PID: 31592
java.lang.IllegalStateException: Could not execute method for android:onClick
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
    at android.view.View.performClick(View.java:5612)
    at android.view.View$PerformClick.run(View.java:22288)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6123)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
 Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22288) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 
 Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
    at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137)
    at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:165)
    at com.example.me.MyApp.RoomDb.Dao.AgentDao_Impl.agentsCount(AgentDao_Impl.java:94)
    at com.example.me.MyApp.View.SignUpActivity.signUpAction(SignUpActivity.java:58)
    at java.lang.reflect.Method.invoke(Native Method) 
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22288) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 

Seems like that problem is related to execution of db operation on main thread. However the sample test code provided in above link does not run on a separate thread:

@Test
    public void writeUserAndReadInList() throws Exception {
        User user = TestUtil.createUser(3);
        user.setName("george");
        mUserDao.insert(user);
        List<User> byName = mUserDao.findUsersByName("george");
        assertThat(byName.get(0), equalTo(user));
    }

Am I missing anything over here? How can I make it execute without crash? Please suggest.

Besides answered 24/5, 2017 at 19:36 Comment(4)
Although written for Kotlin, this article explains the underlying problem very good!Hooker
Take a look to #58533332Nakisha
Look at this answer. This answer work for me https://mcmap.net/q/119723/-room-cannot-access-database-on-the-main-thread-since-it-may-potentially-lock-the-ui-for-a-long-period-of-timeEffy
but when I use thread or coroutine or async so it used to take bit time so my if else condition failing because I'm checking item is exists or not in db if exists so not store the value if not exists then store the value in DB. how can I get rid from this solution ?Minutely
D
77

Database access on main thread locking the UI is the error, like Dale said.

--EDIT 2--

Since many people may come across this answer... The best option nowadays, generally speaking, is Kotlin Coroutines. Room now supports it directly (currently in beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

--EDIT 1--

For people wondering... You have other options. I recommend taking a look into the new ViewModel and LiveData components. LiveData works great with Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Another option is the RxJava/RxAndroid. More powerful but more complex than LiveData. https://github.com/ReactiveX/RxJava

--Original answer--

Create a static nested class (to prevent memory leak) in your Activity extending AsyncTask.

private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {

    //Prevent leak
    private WeakReference<Activity> weakActivity;
    private String email;
    private String phone;
    private String license;

    public AgentAsyncTask(Activity activity, String email, String phone, String license) {
        weakActivity = new WeakReference<>(activity);
        this.email = email;
        this.phone = phone;
        this.license = license;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        return agentDao.agentsCount(email, phone, license);
    }

    @Override
    protected void onPostExecute(Integer agentsCount) {
        Activity activity = weakActivity.get();
        if(activity == null) {
            return;
        }

        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            activity.onBackPressed();
        }
    }
}

Or you can create a final class on its own file.

Then execute it in the signUpAction(View view) method:

new AgentAsyncTask(this, email, phone, license).execute();

In some cases you might also want to hold a reference to the AgentAsyncTask in your activity so you can cancel it when the Activity is destroyed. But you would have to interrupt any transactions yourself.

Also, your question about the Google's test example... They state in that web page:

The recommended approach for testing your database implementation is writing a JUnit test that runs on an Android device. Because these tests don't require creating an activity, they should be faster to execute than your UI tests.

No Activity, no UI.

Deitz answered 24/5, 2017 at 20:30 Comment(13)
Am I supposed to make this huge async task (that you've proposed in code sample) every time I need to access database? It's a dozen or so lines of code instead of one to get some data from db. You proposed also creating new class, but does it mean I need to create new AsyncTask class for each insert/select database call?Francefrancene
You have some other options, yes. You might wanna take look into the new ViewModel and LiveData components. When using the LiveData you don't need the AsyncTask, the object will be notified whenever something changes. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… There's also AndroidRx (although it does pretty much what LiveData does), and Promises. When using the AsyncTask you can architecture in such way that you could include multiple operations in one AsyncTask or separate each.Deitz
@Francefrancene - Kotlin now has async baked in (though it's flagged experimental). See my answer which is comparatively trivial. Samuel Robert's answer covers Rx. I don't see a LiveData answer here, but that might be the better choice if you want an observable.Corot
Using regular AsyncTask doesn't even seem to work now, still getting the illegal state exceptionTavel
@Francefrancene you're telling me you're used to execute Database access on main thread on your entire experience?Favor
@mr5, I'm not sure I understand your message. I asked for advice having no experience.Francefrancene
@Francefrancene it is common for a framework/application to execute their UI controls on main thread thus you don't want to block their rendering with I/O operations such as file reading, network operation, etc. So you should, and must do all database access in other thread to avoid it. Unless you're in a language that supports async/await like C#Favor
Sh*, Am going back to writing SQLite at least it's boilerplate is sensible and actually required. Damn you Google, damn you!Exasperate
SYNCHRONICALLY #58533332Nakisha
but when I use thread or coroutine or async so it used to take bit time so my if else condition failing because I'm checking item is exists or not in db if exists so not store the value if not exists then store the value in DB. how can I get rid from this solution ?Minutely
@SamuelOwino Yeah I'm regretting "upgrading" my app to use Room. I never had a problem with Sqlite and it was making main thread database calls, I assume. But my db was just a few tables with a few tens of records, at most. Super fast, no problems. But Good Lord, they say Room removes "boilerplate" code?! LMFAO. Using Sqlite, and its Cursors, was WAY easier for simple apps. Maybe for some complex app that has thousands of records it's better, but for small normal apps? Pfffft. I've spent a ridiculous amount of time screwing with it and it's not even working yet.Titanesque
Hey, @clamum, I agree with you. I actually moved to GreenDAO, it's magical. A lot of code is generated for you and you don't have to deal with the craziness of Room. You can also save yourself some SQLite code. I would highly recommend GreenDAO greenrobot.org/greendao .Exasperate
@SamuelOwino Huh, haven't heard of that. I may check it out. I've finally got the Room crap working, lol. It took multiples of time longer than it did when I first created my app using old Sqlite. I'll continue with it, but keep GreenDAO in mind. Thanks for your help!Titanesque
C
252

It's not recommended but you can access to database on main thread with allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
Cepeda answered 1/7, 2017 at 3:8 Comment(11)
Room does not allow accessing the database on the main thread unless you called allowMainThreadQueries() on the builder because it might potentially lock the UI for a long period of time. Asynchronous queries (queries that return LiveData or RxJava Flowable) are exempt from this rule since they asynchronously run the query on a background thread when needed.Nunez
Thanks this is very useful for migration, as I want to test Room is working as expected before converting from Loaders to Live DataSophrosyne
@JideGuruTheProgrammer No, it should not. In some cases it could slow your app a lot. The operations should be done asynchronously.Crucifix
@Crucifix There is never a case for making a query on the main thread?Cosset
@JustinMeiners it's just bad practice, you'll be ok doing it as long as the database stays small.Pettifogging
This is useful for unit testing dao functions.Stopper
@Crucifix What did JideGuruTheProgrammer say? And where is his comment? Is it disappeared?Placentation
@TheincredibleJan Man, it was like year ago, so I dont remember. Probably he removed his comment. Its really very bad practice to do any database requests in the main thread of android app.Crucifix
By doing this, even the queries which return LiveData starts running on main thread.Comfrey
@JustinMeiners There is a case. In instrumented tests you may want to run your database on the main thread so new UI, like a new screen, only appears when the data is ready.Sportscast
@AllanVeloso I agree, my comment was questioning the advice that we should never access the database from the main thread.Cosset
C
92

Kotlin Coroutines (Clear & Concise)

AsyncTask is really clunky. Coroutines are a cleaner alternative (just sprinkle a couple of keywords and your sync code becomes async).

// Step 1: add `suspend` to your fun
suspend fun roomFun(...): Int
suspend fun notRoomFun(...) = withContext(Dispatchers.IO) { ... }

// Step 2: launch from coroutine scope
private fun myFun() {
    lifecycleScope.launch { // coroutine on Main
        val queryResult = roomFun(...) // coroutine on IO
        doStuff() // ...back on Main
    }
}

Dependencies (adds coroutine scopes for arch components):

// lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha04'

// viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha04'

-- Updates:
08-May-2019: Room 2.1 now supports suspend
13-Sep-2019: Updated to use Architecture components scope

Corot answered 15/1, 2018 at 14:51 Comment(6)
Do you have any compile error with the @Query abstract suspend fun count() by using suspend keyword? Can you kindly look into this similar question: #48694949Horn
@Horn - Yes I do. My mistake; I was using suspend on a public (unannotated) DAO method that called a protected non-suspend @Query function. When I add the suspend keyword to the internal @Query method too it does indeed fail to compile. It looks like the clever under the hood stuff for suspend & Room clash (as you mention in your other question, the compiled version of suspend is returning a continuation which Room cannot handle).Corot
Makes a lot of sense. I'm going to call it with coroutine functions instead.Horn
@Horn - FYI they added support for suspend in Room 2.1 :)Corot
I have used lifecycleScope.launch(Dispatchers.IO){ // your stuff } and it working fineParlance
I had the same issue, even though I used the lifecycle scope launch, the problem was that I was missing the suspend keyword on my DAO methodsUnderslung
D
77

Database access on main thread locking the UI is the error, like Dale said.

--EDIT 2--

Since many people may come across this answer... The best option nowadays, generally speaking, is Kotlin Coroutines. Room now supports it directly (currently in beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

--EDIT 1--

For people wondering... You have other options. I recommend taking a look into the new ViewModel and LiveData components. LiveData works great with Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Another option is the RxJava/RxAndroid. More powerful but more complex than LiveData. https://github.com/ReactiveX/RxJava

--Original answer--

Create a static nested class (to prevent memory leak) in your Activity extending AsyncTask.

private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {

    //Prevent leak
    private WeakReference<Activity> weakActivity;
    private String email;
    private String phone;
    private String license;

    public AgentAsyncTask(Activity activity, String email, String phone, String license) {
        weakActivity = new WeakReference<>(activity);
        this.email = email;
        this.phone = phone;
        this.license = license;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        return agentDao.agentsCount(email, phone, license);
    }

    @Override
    protected void onPostExecute(Integer agentsCount) {
        Activity activity = weakActivity.get();
        if(activity == null) {
            return;
        }

        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            activity.onBackPressed();
        }
    }
}

Or you can create a final class on its own file.

Then execute it in the signUpAction(View view) method:

new AgentAsyncTask(this, email, phone, license).execute();

In some cases you might also want to hold a reference to the AgentAsyncTask in your activity so you can cancel it when the Activity is destroyed. But you would have to interrupt any transactions yourself.

Also, your question about the Google's test example... They state in that web page:

The recommended approach for testing your database implementation is writing a JUnit test that runs on an Android device. Because these tests don't require creating an activity, they should be faster to execute than your UI tests.

No Activity, no UI.

Deitz answered 24/5, 2017 at 20:30 Comment(13)
Am I supposed to make this huge async task (that you've proposed in code sample) every time I need to access database? It's a dozen or so lines of code instead of one to get some data from db. You proposed also creating new class, but does it mean I need to create new AsyncTask class for each insert/select database call?Francefrancene
You have some other options, yes. You might wanna take look into the new ViewModel and LiveData components. When using the LiveData you don't need the AsyncTask, the object will be notified whenever something changes. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… There's also AndroidRx (although it does pretty much what LiveData does), and Promises. When using the AsyncTask you can architecture in such way that you could include multiple operations in one AsyncTask or separate each.Deitz
@Francefrancene - Kotlin now has async baked in (though it's flagged experimental). See my answer which is comparatively trivial. Samuel Robert's answer covers Rx. I don't see a LiveData answer here, but that might be the better choice if you want an observable.Corot
Using regular AsyncTask doesn't even seem to work now, still getting the illegal state exceptionTavel
@Francefrancene you're telling me you're used to execute Database access on main thread on your entire experience?Favor
@mr5, I'm not sure I understand your message. I asked for advice having no experience.Francefrancene
@Francefrancene it is common for a framework/application to execute their UI controls on main thread thus you don't want to block their rendering with I/O operations such as file reading, network operation, etc. So you should, and must do all database access in other thread to avoid it. Unless you're in a language that supports async/await like C#Favor
Sh*, Am going back to writing SQLite at least it's boilerplate is sensible and actually required. Damn you Google, damn you!Exasperate
SYNCHRONICALLY #58533332Nakisha
but when I use thread or coroutine or async so it used to take bit time so my if else condition failing because I'm checking item is exists or not in db if exists so not store the value if not exists then store the value in DB. how can I get rid from this solution ?Minutely
@SamuelOwino Yeah I'm regretting "upgrading" my app to use Room. I never had a problem with Sqlite and it was making main thread database calls, I assume. But my db was just a few tables with a few tens of records, at most. Super fast, no problems. But Good Lord, they say Room removes "boilerplate" code?! LMFAO. Using Sqlite, and its Cursors, was WAY easier for simple apps. Maybe for some complex app that has thousands of records it's better, but for small normal apps? Pfffft. I've spent a ridiculous amount of time screwing with it and it's not even working yet.Titanesque
Hey, @clamum, I agree with you. I actually moved to GreenDAO, it's magical. A lot of code is generated for you and you don't have to deal with the craziness of Room. You can also save yourself some SQLite code. I would highly recommend GreenDAO greenrobot.org/greendao .Exasperate
@SamuelOwino Huh, haven't heard of that. I may check it out. I've finally got the Room crap working, lol. It took multiples of time longer than it did when I first created my app using old Sqlite. I'll continue with it, but keep GreenDAO in mind. Thanks for your help!Titanesque
A
53

For all the RxJava or RxAndroid or RxKotlin lovers out there

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Aggressor answered 25/10, 2017 at 6:37 Comment(5)
If I put this code inside a method, how to return the result from database operation?Bosco
@EggakinBaconwalker I have override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() } where applySchedulers() I just do fun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())Notornis
This won't work for IntentService. Because IntentService will be done once it's thread is completed.Gladstone
@UmangKothari You don't get the exception if you're on IntentService#onHandleIntent because this method executes on worker thread so you wouldn't need any threading mechanism there to do the Room database operationAggressor
@SamuelRobert, Yes agree my bad. It slipped my mind.Gladstone
C
36

You cannot run it on main thread instead use handlers, async or working threads . A sample code is available here and read article over room library here : Android's Room Library

/**
 *  Insert and get data using Database Async way
 */
AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        // Insert Data
        AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew"));

        // Get Data
        AppDatabase.getInstance(context).userDao().getAllUsers();
    }
});

If you want to run it on main thread which is not preferred way .

You can use this method to achieve on main thread Room.inMemoryDatabaseBuilder()

Cantilena answered 6/11, 2017 at 6:25 Comment(4)
If I use this method to get the data (only getAllUsers()in this case), how return the data from this method? It appears an error, if I put the word "return" inside the "run".Bosco
make an interface method somewhere and add anonymous class to get data from here .Cantilena
This is the simplest solution for inserting/updating.Growing
AsyncTask.execute() is deprecatedValerle
M
27

With lambda its easy to run with AsyncTask

 AsyncTask.execute(() -> //run your query here );
Messer answered 14/3, 2019 at 1:43 Comment(2)
This is convenient, thanks. By the way, Kotlin is even easier: AsyncTask.execute { }Baikal
but how do you get the result using this method?Mervinmerwin
L
16

Just do the database operations in a separate Thread. Like this (Kotlin):

Thread {
   //Do your database´s operations here
}.start()
Leenaleeper answered 30/4, 2019 at 18:45 Comment(1)
Not a great way. You have no control over the running thread, so you can't stop it if the activity for example closes.Custos
G
14

Simply you can use this code for solve it:

Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        appDb.daoAccess().someJobes();//replace with your code
                    }
                });

Or in lambda you can use this code:

Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());

You can replace appDb.daoAccess().someJobes() with your own code;

Georgiannageorgianne answered 2/2, 2019 at 0:0 Comment(0)
C
13

With the Jetbrains Anko library, you can use the doAsync{..} method to automatically execute database calls. This takes care of the verbosity problem you seemed to have been having with mcastro's answer.

Example usage:

    doAsync { 
        Application.database.myDAO().insertUser(user) 
    }

I use this frequently for inserts and updates, however for select queries I reccommend using the RX workflow.

Cartography answered 7/8, 2017 at 4:18 Comment(0)
M
10

As asyncTask are deprecated we may use executor service. OR you can also use ViewModel with LiveData as explained in other answers.

For using executor service, you may use something like below.

public class DbHelper {

    private final Executor executor = Executors.newSingleThreadExecutor();

    public void fetchData(DataFetchListener dataListener){
        executor.execute(() -> {
                Object object = retrieveAgent(agentId);
                new Handler(Looper.getMainLooper()).post(() -> {
                        dataListener.onFetchDataSuccess(object);
                });
        });
    }
}

Main Looper is used, so that you can access UI element from onFetchDataSuccess callback.

Muzzy answered 5/1, 2020 at 19:22 Comment(0)
G
9

You have to execute request in background. A simple way could be using an Executors :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Greysun answered 11/7, 2018 at 12:50 Comment(1)
how do you return result ?Mervinmerwin
M
8

Room Database does not allow you to execute a database IO operation(Background operation) in Main thread, unless you use allowMainThreadQueries() with database builder. But it is a bad approach.


Recommended Approach:
Here I am using some code from my current project.

Add suspend keyword before your method in Repository

class FavRepository @Inject constructor(private val dao: WallpaperDao) {
    suspend fun getWallpapers(): List<Wallpaper> =  dao.getWallpapers()
}

In your viewmodel class first you need to execute your database operation with Coroutine Dispature IO for fetching data from room database. Then update your value with Coroutine Dispature MAIN.

@HiltViewModel
class FavViewModel @Inject constructor(repo: FavRepository, @ApplicationContext context: Context) : ViewModel() {
    var favData = MutableLiveData<List<Wallpaper>>()
    init {
        viewModelScope.launch(Dispatchers.IO){
            val favTmpData: List<Wallpaper> = repo.getWallpapers()
            withContext(Dispatchers.Main){
                favData.value = favTmpData
            }
        }
    }
}

Now you use your viewmodel's data by observing from Activity/ Fragment.

Hope this will be helpful for you :) .

Marketable answered 30/1, 2022 at 10:38 Comment(1)
Great approach! Helped to resolve the issue that I was facing. Thanks a lotLeahleahey
C
5

For quick queries you can allow room to execute it on UI thread.

AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
        AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();

In my case I had to figure out of the clicked user in list exists in database or not. If not then create the user and start another activity

       @Override
        public void onClick(View view) {



            int position = getAdapterPosition();

            User user = new User();
            String name = getName(position);
            user.setName(name);

            AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase();
            UserDao userDao = appDatabase.getUserDao();
            ArrayList<User> users = new ArrayList<User>();
            users.add(user);
            List<Long> ids = userDao.insertAll(users);

            Long id = ids.get(0);
            if(id == -1)
            {
                user = userDao.getUser(name);
                user.setId(user.getId());
            }
            else
            {
                user.setId(id);
            }

            Intent intent = new Intent(mContext, ChatActivity.class);
            intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user));
            mContext.startActivity(intent);
        }
    }
Cowles answered 7/8, 2017 at 0:50 Comment(0)
T
5

An elegant RxJava/Kotlin solution is to use Completable.fromCallable, which will give you an Observable which does not return a value, but can observed and subscribed on a different thread.

public Completable insert(Event event) {
    return Completable.fromCallable(new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            return database.eventDao().insert(event)
        }
    }
}

Or in Kotlin:

fun insert(event: Event) : Completable = Completable.fromCallable {
    database.eventDao().insert(event)
}

You can the observe and subscribe as you would usually:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)
Tortuga answered 2/11, 2017 at 20:52 Comment(0)
C
5

You can allow database access on the main thread but only for debugging purpose, you shouldn't do this on production.

Here is the reason.

Note: Room doesn't support database access on the main thread unless you've called allowMainThreadQueries() on the builder because it might lock the UI for a long period of time. Asynchronous queries—queries that return instances of LiveData or Flowable—are exempt from this rule because they asynchronously run the query on a background thread when needed.

Cannikin answered 12/12, 2017 at 13:21 Comment(0)
H
3

The error message,

Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.

Is quite descriptive and accurate. The question is how should you avoid accessing the database on the main thread. That is a huge topic, but to get started, read about AsyncTask (click here)

-----EDIT----------

I see you are having problems when you run a unit test. You have a couple of choices to fix this:

  1. Run the test directly on the development machine rather than on an Android device (or emulator). This works for tests that are database-centric and don't really care whether they are running on a device.

  2. Use the annotation @RunWith(AndroidJUnit4.class) to run the test on the android device, but not in an activity with a UI. More details about this can be found in this tutorial

Hopeh answered 24/5, 2017 at 19:41 Comment(2)
I understand your point, my assumption is that the same point is valid when you try to test any db operation through JUnit. However in developer.android.com/topic/libraries/architecture/room.html the sample test method writeUserAndReadInList does not invoke the insert query on background thread. Am I missing anything over here? Please suggest.Besides
Sorry I missed the fact that this was the test having problems. I'll edit my answer to add some more information.Hopeh
K
3

Update: I also got this message when I was trying to build a query using @RawQuery and SupportSQLiteQuery inside the DAO.

@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
    //return getMyList(); -->this is ok

    return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error

Solution: build the query inside the ViewModel and pass it to the DAO.

public MyViewModel(Application application) {
...
        list = Transformations.switchMap(searchParams, params -> {

            StringBuilder sql;
            sql = new StringBuilder("select  ... ");

            return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));

        });
    }

Or...

You should not access the database directly on the main thread, for example:

 public void add(MyEntity item) {
     appDatabase.myDao().add(item); 
 }

You should use AsyncTask for update, add, and delete operations.

Example:

public class MyViewModel extends AndroidViewModel {

    private LiveData<List<MyEntity>> list;

    private AppDatabase appDatabase;

    public MyViewModel(Application application) {
        super(application);

        appDatabase = AppDatabase.getDatabase(this.getApplication());
        list = appDatabase.myDao().getItems();
    }

    public LiveData<List<MyEntity>> getItems() {
        return list;
    }

    public void delete(Obj item) {
        new deleteAsyncTask(appDatabase).execute(item);
    }

    private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        deleteAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().delete((params[0]));
            return null;
        }
    }

    public void add(final MyEntity item) {
        new addAsyncTask(appDatabase).execute(item);
    }

    private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        addAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().add((params[0]));
            return null;
        }

    }
}

If you use LiveData for select operations, you don't need AsyncTask.

Klockau answered 5/1, 2018 at 1:14 Comment(0)
P
3

If you are more comfortable with Async task:

  new AsyncTask<Void, Void, Integer>() {
                @Override
                protected Integer doInBackground(Void... voids) {
                    return Room.databaseBuilder(getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration()
                            .build()
                            .getRecordingDAO()
                            .getAll()
                            .size();
                }

                @Override
                protected void onPostExecute(Integer integer) {
                    super.onPostExecute(integer);
                    Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
                }
            }.execute();
Precontract answered 9/2, 2018 at 5:24 Comment(0)
R
3

You can use Future and Callable. So you would not be required to write a long asynctask and can perform your queries without adding allowMainThreadQueries().

My dao query:-

@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();

My repository method:-

public UserData getDefaultData() throws ExecutionException, InterruptedException {

    Callable<UserData> callable = new Callable<UserData>() {
        @Override
        public UserData call() throws Exception {
            return userDao.getDefaultData();
        }
    };

    Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);

    return future.get();
}
Rataplan answered 7/8, 2018 at 6:43 Comment(6)
This is why we are using Callable/Future because android does not allows queries to run on main thread. As asked in the qus aboveRataplan
I mean, although code from your answer make query in background thread, main thread is blocked and waiting when query finished. So at the end it is not much better than allowMainThreadQueries(). Main thread still blocked in both casesPreteritive
@Preteritive why "main thread is blocked and waiting when query finished"?Matriarchy
@DinhQuangTuan, because that is how Future.get() method works: "Waits if necessary for the computation to complete, and then retrieves its result."Preteritive
@Preteritive so what should we use in this case?Matriarchy
@DinhQuangTuan, don't use this approach and check out other upvoted answers in this thread (Coroutines, for instance)Preteritive
P
2

Room Database does not allow you to execute a database IO operation in the Main thread. So In kotlin performed your query call inside CoroutineScope(IO). e.g.

suspend fun executrQuery(){
     CoroutineScope(IO).launch {
         // Write your code inside.
         }
}

And Another way is to use allowMainThreadQueries like below.

AppDatabase db =Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();
Pilose answered 25/4, 2023 at 16:5 Comment(0)
H
1

In my opinion the right thing to do is to delegate the query to an IO thread using RxJava.

I have an example of a solution to an equivalent problem I've just encountered.

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            //Creating view model requires DB access
            homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
        }).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    mAdapter = new MyAdapter(homeViewModel.getExams());
                    recyclerView.setAdapter(mAdapter);
                    ((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
                },
                error -> error.printStackTrace()
        );

And if we want to generalize the solution:

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            someTaskThatTakesTooMuchTime();
        }).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
                },
                error -> error.printStackTrace()
        );
Hartmunn answered 2/4, 2020 at 19:43 Comment(0)
S
1

Add .allowMainThreadQueries() in database file

@Database(entities = [Country::class], version = 1)
abstract class CountryDatabase: RoomDatabase() {
    abstract fun getCountryDao(): CountryDao
    companion object {
        @Volatile
        private var instance: CountryDatabase? = null
        private val LOCK = Any()

        operator fun invoke(context: Context) = instance ?:
        synchronized(LOCK) {
            instance ?:
            createDatabase(context).also { instance = it }
        }
        private fun createDatabase(context: Context) =
            Room.databaseBuilder(
                context.applicationContext,
                CountryDatabase::class.java,
                "country_db"
            ).allowMainThreadQueries()
             .build()
    }
}
Selfrespect answered 11/10, 2021 at 21:53 Comment(0)
C
1

Add Dispatchers.IO at the end of flow like this:

flow { ... }.flowOn(Dispatchers.IO)
Catullus answered 16/11, 2021 at 23:28 Comment(0)
I
1

call your query in coroutine with (Dispatchers.IO) because we need to do db operation in the background.

please check the attached screenshot below : enter image description here

Incongruous answered 8/7, 2022 at 19:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.