20

I have a room persistant database insertion method which looks like this:

@Dao
public interface CountriesDao{

    @Insert(onConflict = REPLACE)
    List<Long> addCountries(List<CountryModel> countryModel);
}

I realize that this can't be run on the main thread. Here is how I define my database:

Room.inMemoryDatabaseBuilder(context.getApplicationContext(), MyDatabase.class).build();

I am trying to use rxjava2 so that I don't run on main thread. I have created the following method:

public void storeCountries(List<CountryModel> countriesList) {
        Observable.just(db.countriesDao().addCountries(countriesList))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultSubscriber<List<Long>>(){
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                super.onSubscribe(d);
            }

            @Override
            public void onNext(@NonNull List<Long> longs) {
                super.onNext(longs);
                Timber.d("insert countries transaction complete");
            }

            @Override
            public void onError(@NonNull Throwable e) {
                super.onError(e);
                Timber.d("error storing countries in db"+e);
            }

            @Override
            public void onComplete() {
                Timber.d("insert countries transaction complete");
            }
        });
    }

For me this is clearly now running on another thread. NOT the main thread but when I run this code i get the following error:

The full stack trace is below. Why is this happening ?

Process: com.mobile.myapp.staging, PID: 12990
java.lang.IllegalStateException: Fatal Exception thrown on Scheduler. 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 io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111) 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:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 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 android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:138) at android.arch.persistence.room.RoomDatabase.beginTransaction(RoomDatabase.java:185) at com.mobile.myapp.data.room.dao.CountriesDao_Impl.addCountries(CountriesDao_Impl.java:165) at com.mobile.myapp.data.repositories.CountryRepository.storeCountries(CountryRepository.java:42) at com.mobile.myapp.UI.mvp.Presenters.SignUpPresenter.cacheCountries(SignUpPresenter.java:40) at com.mobile.myapp.UI.mvp.Presenters.SignUpPresenter$CountriesSubscriber.onNext(SignUpPresenter.java:60) at com.mobile.myapp.UI.mvp.Presenters.SignUpPresenter$CountriesSubscriber.onNext(SignUpPresenter.java:49) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:200) at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252) at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109) 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:6077)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)

Not important but if you need to know what defaultSubscriber class looks like here it is:

DefaultSubscriber.java

public class DefaultSubscriber<T> implements Observer<T> {

Disposable disposable;

@Override
public void onSubscribe(@NonNull Disposable d) {
    disposable = d;
}

@Override
public void onNext(@NonNull T t) {

}

@Override
public void onError(@NonNull Throwable e) {
    Timber.e(e);
}

@Override
public void onComplete() {

}

public void unsubscribe(){
    if(disposable!=null && !disposable.isDisposed()){
        disposable.dispose();
    }
  }
}
Levon Petrosyan
  • 8,815
  • 8
  • 54
  • 65
j2emanue
  • 60,549
  • 65
  • 286
  • 456

6 Answers6

33

This is a common mistake: just() won't execute the "code" within its parenthesis as just takes a value, not a computation. You need fromCallable:

Observable.fromCallable(() -> db.countriesDao().addCountries(countriesList))
akarnokd
  • 69,132
  • 14
  • 157
  • 192
17

Even better, you could use a Completable. Its description: Represents a computation without any value but only indication for completion or exception.

Completable.fromAction(() -> db.countriesDao().addCountries(list)).subscribe();
Thirumalvalavan
  • 2,660
  • 1
  • 30
  • 32
dynamitem
  • 1,647
  • 6
  • 25
  • 45
12

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.

So your code can be like this

Completable.fromAction(() -> db.countriesDao()
                .addCountries(list))
                .subscribeOn(Schedulers.io())
                .subscribe();
Levon Petrosyan
  • 8,815
  • 8
  • 54
  • 65
6

From room room 2.1.0-alpha02, you can use (Completeable, Single, Maybe) when insertion ( https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757)

Example

@Dao
interface UserDao{
     @Insert
     Completable insert(final User user); // currently, we must put final before user variable or you will get error when compile
}

Using

db.userDao().insert(user).subscribeOn(Schedulers.io()).subscribe(new Action() {
    @Override
    public void run() throws Exception {
        // success
    }
}, new Consumer < Throwable > () {
    @Override
    public void accept(Throwable throwable) throws Exception {
        // error
    }
});
Linh
  • 57,942
  • 23
  • 262
  • 279
1

You can also use the Single observable

        Single.create(new SingleOnSubscribe<List<Long>>() {
        @Override
        public void subscribe(SingleEmitter<List<Long>> emitter) throws Exception {
            try {
                List<Long> ids = db.countriesDao().addCountries(countriesList);
                emitter.onSuccess(ids);
            } catch (Throwable t) {
                emitter.onError(t);
            }
        }})
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribeWith(new DisposableSingleObserver<Long>() {
        @Override
        public void onSuccess(List<Long> ids) {

        }

        @Override
        public void onError(Throwable e) {

        }
});
Farid Z
  • 960
  • 1
  • 9
  • 18
0

Ensure you have added both dependencies, "implementation 'io.reactivex.rxjava3:rxjava:3.0.0' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'"