Room: Cannot access database on the main thread *but*

Viewed 438

I've got this piece of code that I'm trying to run in my Android app:

            ServerDatabase.getInstance(ListServersActivity.this).serverDao().getAll().observe(ListServersActivity.this, new Observer<List<Server>>() {
                @Override
                public void onChanged(List<Server> servers) {
                    serverListAdapter.setServers(servers);
                }
            });

I'm trying to run that on the UI thread, so I'm getting this error that looks pretty straight forward and there are lots of entries about it in StackOverflow:

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

Now, the problem with this and what makes it different from previous questions I think, is that I'm running it in the main thread on purpose calling runOnUiThread because when I was doing it in a background thread I've got this other error:

Cannot invoke observe on a background thread

So screwed if I run it on the main thread, and screwed if I run it on a background thread. How do I solve this conundrum?

Adding more info, ServerDao is a Room Dao interface so the actual implemenation is generated by Room. This is the declaration of the involved method:

public interface ServerDao {
    @Query("SELECT * FROM server ORDER BY id ASC")
    LiveData<List<Server>> getAll();
// ...
}

This is the stack when running in the background thread:

2020-03-13 13:36:37.684 24241-24939/com.daon.identityx.docscan E/AndroidRuntime: FATAL EXCEPTION: Thread-13 Process: com.daon.identityx.docscan, PID: 24241 java.lang.IllegalStateException: Cannot invoke observe on a background thread at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:443) at androidx.lifecycle.LiveData.observe(LiveData.java:171) at com.daon.identityx.docscan.ui.activity.ListServersActivity.initRecyclerViewAdapter(ListServersActivity.java:92) at com.daon.identityx.docscan.ui.activity.ListServersActivity.access$000(ListServersActivity.java:33) at com.daon.identityx.docscan.ui.activity.ListServersActivity$1.run(ListServersActivity.java:62) at java.lang.Thread.run(Thread.java:764) 2020-03-13 13:36:37.856 24241-24939/com.daon.identityx.docscan E/UncaughtException: java.lang.IllegalStateException: Cannot invoke observe on a background thread at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:443) at androidx.lifecycle.LiveData.observe(LiveData.java:171) at com.daon.identityx.docscan.ui.activity.ListServersActivity.initRecyclerViewAdapter(ListServersActivity.java:92) at com.daon.identityx.docscan.ui.activity.ListServersActivity.access$000(ListServersActivity.java:33) at com.daon.identityx.docscan.ui.activity.ListServersActivity$1.run(ListServersActivity.java:62) at java.lang.Thread.run(Thread.java:764) 2020-03-13 13:36:37.899 24241-24595/com.daon.identityx.docscan E/CrashlyticsCore: Unexpected method invoked on AppMeasurement.EventListener: onEvent(java.lang.String, java.lang.String, android.os.Bundle, java.lang.Long); returning null 2020-03-13 13:36:54.223 24960-25084/com.daon.identityx.docscan E/FirebaseCrash: Unable to parse Json response string to get message: No value for crashes

And this is the stack when running in the UI thread:

2020-03-13 13:45:17.552 26410-26410/com.daon.identityx.docscan E/AndroidRuntime: FATAL EXCEPTION: main Process: com.daon.identityx.docscan, PID: 26410 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.daon.identityx.docscan/com.daon.identityx.docscan.ui.activity.ListServersActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3114) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7050) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965) Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267) at androidx.room.RoomDatabase.query(RoomDatabase.java:323) at androidx.room.util.DBUtil.query(DBUtil.java:83) at com.daon.identityx.docscan.database.models.ServerDao_Impl.getCount(ServerDao_Impl.java:300) at com.daon.identityx.docscan.repository.ServerDatabase.addDefaultDataIfEmpty(ServerDatabase.java:30) at com.daon.identityx.docscan.repository.ServerDatabase.getInstance(ServerDatabase.java:23) at com.daon.identityx.docscan.ui.activity.ListServersActivity.initRecyclerViewAdapter(ListServersActivity.java:88) at com.daon.identityx.docscan.ui.activity.ListServersActivity.onCreate(ListServersActivity.java:60) at android.app.Activity.performCreate(Activity.java:7327) at android.app.Activity.performCreate(Activity.java:7318) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3094) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257)  at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:214)  at android.app.ActivityThread.main(ActivityThread.java:7050)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)  2020-03-13 13:45:17.694 26410-26410/com.daon.identityx.docscan E/UncaughtException: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.daon.identityx.docscan/com.daon.identityx.docscan.ui.activity.ListServersActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3114) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7050) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965) Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267) at androidx.room.RoomDatabase.query(RoomDatabase.java:323) at androidx.room.util.DBUtil.query(DBUtil.java:83) at com.daon.identityx.docscan.database.models.ServerDao_Impl.getCount(ServerDao_Impl.java:300) at com.daon.identityx.docscan.repository.ServerDatabase.addDefaultDataIfEmpty(ServerDatabase.java:30) at com.daon.identityx.docscan.repository.ServerDatabase.getInstance(ServerDatabase.java:23) at com.daon.identityx.docscan.ui.activity.ListServersActivity.initRecyclerViewAdapter(ListServersActivity.java:88) at com.daon.identityx.docscan.ui.activity.ListServersActivity.onCreate(ListServersActivity.java:60) at android.app.Activity.performCreate(Activity.java:7327) at android.app.Activity.performCreate(Activity.java:7318) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3094) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257)  at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:214)  at android.app.ActivityThread.main(ActivityThread.java:7050)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)  2020-03-13 13:45:17.732 26410-26729/com.daon.identityx.docscan E/CrashlyticsCore: Unexpected method invoked on AppMeasurement.EventListener: onEvent(java.lang.String, java.lang.String, android.os.Bundle, java.lang.Long); returning null 2020-03-13 13:45:26.315 27259-27416/com.daon.identityx.docscan E/FirebaseCrash: Unable to parse Json response string to get message: No value for crashes

1 Answers

You have a few methods on your DAO. Those that return reactive types like LiveData, such as getAll(), can be called on the main application thread. Room will arrange to do the work on background threads for you.

However, you also appear to have a getCount() method that probably looks something like this:

@Query("SELECT COUNT(*) FROM something")
public long getCount();

That is not returning a reactive type, so it will be a synchronous call. That sort of call you cannot make on the main application thread without encountering the "can't do this on the main application thread" exception.

Either:

  • Have this method also return a reactive type

  • Only call this method from a background thread

  • Switch to Kotlin and coroutines, where you can have the best of both worlds (syntax that looks synchronous even though in reality the I/O is asynchronous)

  • Disable the background-thread check via allowMainThreadQueries() on your RoomDatabase.Builder, and be prepared to be yelled at by co-workers and/or users for doing disk I/O on the main application thread

Related