1

I'm getting that famous error "Cannot access database on the main thread since it may potentially lock the UI for a long periods of time" But from what I understand, I'm not accessing the database in main thread since I'm executing the call inside a Runnable executed by a ThreadPoolExecutor. What am I doing wrong?

In the following method, I'm using a runnable to obtain data from network and store it in a local database.

private void refresh() {
    executor.execute(() -> {
        if (!dataSource.hasData()) {
            recipeService.getAllRecipes().enqueue(new Callback<List<Recipe>>() {
                @Override
                public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) {
                    dataSource.save(response.body());
                }

                @Override
                public void onFailure(Call<List<Recipe>> call, Throwable t) {
                    throw new RuntimeException(t);
                }
            });
        }

    });
}

DataSource.save:

@Override
    public void save(List<Recipe> recipes) {
        for (Recipe recipe : recipes) {
            recipeDao.insert(recipe);
            int count = 1;

            for (Ingredient ingredient : recipe.getIngredients()) {
                ingredient.setId(count++);
                ingredient.setRecipeId(recipe.getId());
                ingredientDao.insert(ingredient);
            }

            for (Step step : recipe.getSteps()) {
                step.setRecipeId(recipe.getId());
                stepDao.insert(step);
            }

        }
    }

The executor is defined as:

@Provides
Executor executor() {
    return new ThreadPoolExecutor(4, 8, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
}

Error I'm getting:

06-18 03:03:38.653 27231-27231/com.github.alexpfx.udacity.nanodegree.android.baking_app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.github.alexpfx.udacity.nanodegree.android.baking_app, PID: 27231
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.beginTransaction(RoomDatabase.java:184)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.data.local.RecipeDao_Impl.insert(RecipeDao_Impl.java:89)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.recipe.RecipeLocalDataSource.save(RecipeLocalDataSource.java:34)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.recipe.RecipesRepositoryImpl$1.onResponse(RecipesRepositoryImpl.java:49)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
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:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
alexpfx
  • 6,412
  • 12
  • 52
  • 88
  • 1
    when not sure simply call `Log.d` with the value of `Thread.currentThread()` before calling your `dataSource.save(response.body());` and you will see that , yes, it is called in the UI thread – pskink Jun 18 '17 at 06:21
  • I call the log just before the conditional (hasData?) and before save, the result is: refresh: Thread[pool-2-thread-1,5,main] refresh: Thread[main,10,main] What this means? – alexpfx Jun 18 '17 at 06:24
  • `Thread[main,10,main]` - this is UI thread – pskink Jun 18 '17 at 06:25
  • I thought the fact it is wrapping by a Runnable, it executes in different thread. But I will try what you suggest. – alexpfx Jun 18 '17 at 06:31
  • yes, it works. off course, the retrofit callback is executed in the main thread. .. I had not realized... – alexpfx Jun 18 '17 at 06:34
  • the runnable is there: executor.execute(() -> {} it's a lambda expression. – alexpfx Jun 18 '17 at 06:36
  • ahh, sure ;-) i missed that – pskink Jun 18 '17 at 06:37
  • 1
    use this https://square.github.io/retrofit/2.x/retrofit/retrofit2/Retrofit.Builder.html to setup your own callback executor – pskink Jun 18 '17 at 06:42
  • Please follow the same answer for your question. [Room on main thread](https://stackoverflow.com/a/47130962/1151710) – Rizvan Nov 06 '17 at 06:28
  • 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 issue? – Menu Aug 26 '21 at 11:40

2 Answers2

2

seems that your callback.onresponce() method call is from UI thread

changing your onResponse method like this can help

@Override
public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) {
AsyncTask.execute(new Runnable() {
                    @Override
                    public void run() {
                        dataSource.save(response.body());
                    }
                });

            }
snersesyan
  • 1,647
  • 17
  • 26
  • 1
    You could turn this "comment" into a full-fledged answer by adding more detail and an example of how to fix it. – David Oct 23 '17 at 16:42
2

You can change your implementation to:

private void refresh() {

        if (!dataSource.hasData()) {
            recipeService.getAllRecipes().enqueue(new Callback<List<Recipe>>() {
                @Override
                public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) {

                executor.execute(() -> {
                    dataSource.save(response.body());
                  });
                }

                @Override
                public void onFailure(Call<List<Recipe>> call, Throwable t) {
                    throw new RuntimeException(t);
                }
            });
        }
}

The onResponse function is always called in the UIThread.

Fredy Mederos
  • 2,506
  • 15
  • 13