Rxjava2 just method - how to run room insertion on another thread? - android

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();
}
}
}

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))

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();

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();

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
}
});

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) {
}
});

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

Related

Replace AsyncTask with Rxjava for Room Database

I'm trying to replace AsyncTask with RxJava, since Async is deprecated in Android 11. however I cannnot get RxJava to work with Room,
This is currently working code with Asynctask:
In Repository:
public void insertItems(List<Items> items){
new insertItemsAsyncTask(PrjDao).execute(items);
}
private static class insertItemAsyncTask extends AsyncTask<List<Items>, Void, Void>{
private PrjDao prjDao;
private insertItemAsyncTask(PrjDao prjDao){
this.prjDao = prjDao;
}
#Override
protected Void doInBackground(List<Items>... lists) {
prjDao.insertItems(lists[0]);
return null;
}
}
in DAO
#Insert
void insertItems(List<Items> items);
I replaced repository code with:
public void insertItems(List<Items> items){
Completable.fromAction(() -> prjDao.insertItems(items)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onComplete() {
}
#Override
public void onError(#NonNull Throwable e) {
}
});
}
However It is not working, even though I managed to get Log output in onComplete.
Try:
DAO:
#Insert
Completable insertItems(List<Items> items);
Repository:
public void insertItems(List<Items> items){
prjDao.insertItems(items))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
}
Or better so:
Repository:
public Completable insertItems(List<Items> items){
return prjDao.insertItems(items))
}
And then, subscribe to the completable and handle subscribe callbacks where you actually call insertItems().
insertItems(items)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
In my opinion, repository should only provide bridge to the DB interface, and the caller should be the one to handle the subscribe callbacks, as each caller may want to handle the callbacks differently.
UPDATE
To use rxjava with room, please check that you have all needed dependencies in your build.gradle file:
implementation "androidx.room:room-runtime:[roomVersion]"
implementation "androidx.room:room-rxjava2:[roomVersion]"
annotationProcessor "androidx.room:room-compiler:[roomVersion]"
I currently use roomVersion 2.2.5
Here is a simple working demo of room + rxjava I just created, maybe you will find differences there:
https://github.com/phamtdat/RxRoomDemo

Android paging works not as expected

I tried to use paging in my project. Unfortunately, it works not as I expected. I expected that the liveDataObserver will work after callBack.onResult.But in fact, the liveDataObserver observes immediately when the loadInitial finished.The callBack works later, and didn't post data to the observer.
The code:
First I wrote a class extend PageKeyedDataSource and interface SingleCreator
public class MyPagingDataSource<T> extends PageKeyedDataSource<Integer, T>
public interface SingleCreator<T> {
SingleSubscribeProxy<Page<T>> createSingle(int page, int pageSize);
}
Then the constructor of MyPagingDataSource:
public MyPagingDataSource(SingleCreator<T> singleCreator) {
this.singleCreator = singleCreator;
}
And override loadInitial:
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, T> callback) {
singleCreator.createSingle(1, params.requestedLoadSize)
.subscribe(new SingleObserver<Page<T>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Page<T> ts) {
callback.onResult(ts.list, ts.pageNumber, ts.total, ts.pageNumber - 1, ts.pageNumber + 1);
Timber.d("registerLiveData" + ts.list.size());
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
});
try {
//when I add this, observer will work after callback
//And if not observer works before callback.onResult
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Then the datasourceFactory is just newInstanced in viewModel:
public LiveData<PagedList<MyData>> page = loadPageData();
public LiveData<PagedList<MyData>> loadPageData() {
return new LivePagedListBuilder<>(new DataSource.Factory<Integer, MyData>() {
#Override
public DataSource<Integer, MyData> create() {
return new HBPagingDataSource<>((page, pageSize) -> loadPageSingle(page, pageSize));
}
}, 2).build();
}
the single
private SingleSubscribeProxy<Page<MyData>> loadPageSingle(int pageNum, int pageSize) {
return mModel.loadMyDates(pageNum, pageSize)
.doOnError(Throwable::printStackTrace)
.as(autoDisposable(this));
}
at fragment
mViewModel.page.observe(this, myDatas -> {
Timber.d("registerLiveData%s", myDatas.size());
myAdapter.submitList(myDatas);
});
Maybe related things:
I wrote subscribeOn and observeOn in retrofit's callAdapter
The viewModel is a scopeProvider since I'm using autoDispose
I tried some example in github. And it seems, the setValue for pageLivedata is always work after loadInitial. In this case, how can I use single?
It's seems solved.
The error is because schedule the thread using rxjava.
It makes single and datasource work in different thread.
In this case, callback onResult run after the observer.
So, I updated the callAdapter where I wrote subscribeOn and observeOn for single.
Filter by className when It's Page class, it won't do subscribeOn and observeOn.
Now the conclusion is, let paging handle the thread.

Android, Retrofit , & RxJava: "Realm access from incorrect thread"

I have a realm object stored locally that I need to change > then upload to my backend. So inside userService.updateUser() I get my current user object by calling: Realm realm = Realm.getDefaultInstance() and passing that User object on to my retrofit2 call, but I get the Realm error:
Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
So I'm subscribing on Schedulers.newThread() and observing on AndroidSchedulers.mainThread() so I'm thinking thats why the error is thrown...how can I avoid this issue?
mCompositeDisposable.add( userService.updateUser()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<User>(){
#Override
public void onNext(User user) {
if(mProgressDlg != null) mProgressDlg.dismiss();
}
#Override
public void onError(Throwable t) {
if(mProgressDlg != null) mProgressDlg.dismiss();
alertDlg.showIt(mResources.getString(R.string.err_saving_profile),
stringFormatter.getApiErrorMsg(t), "",
"", mParentActivity, JAlertDialog.POSITIVE,null);
}
#Override
public void onComplete() { }
}));
This works:
mCompositeDisposable.add( userService.updateUser( Realm.getDefaultInstance().copyFromRealm(mUser) )
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith
...

RxJava: Continue next iteration even if error occurs

I'm using RxSearchView to emit out the results of a search query from an API to a recyclerview. However, if one of those query fails, onError() is called(which is expected) but the subscription as a whole is also canceled. Subsequent queries are not executed at all.
How should i modify the code so that the call to onError() is prevented when a query fails and the next incoming queries are executed normally?
Here's a code snippet:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService.getSearchResults(query))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
P.S: I am using switchMap() so that the results of old queries are ignored, if the results of new query has arrived.
You have to handle this error and return an object instead. You can do it, for example, by using onErrorResumeNext operator with apiService.getSearchResults(query) call. What you are going to return - depends on you, you can even return null if you want, but better to create some wrapper which can carry both response status flag and normal response if received.
Something like:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService
.getSearchResults(query)
.onErrorResumeNext(error -> null)
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse != null && searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
Of course, this is naive example with using null, in reality you need to write error handling logic. Better to return wrapper, because if using RxJava 2, then it doesn't support null.

Prevent OnErrorNotImplementedException

I want to achieve that if i call the Obervable.subscribe(Action1) method, it does not throw OnErrorNotImplementedException anywhere, but if i call Obervable.subscribe(Action1, Action1), the second action is called when an error is raised as normal. I tried two ways:
.onErrorResumeNext(Observable.empty())
This way OnErrorNotImplementedException is not thrown, however if i pass also the second action, the action is never called either.
Second:
.lift(new Observable.Operator<T, T>() {
#Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
return new Subscriber<T>() {
#Override
public void onCompleted() {
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
#Override
public void onError(Throwable e) {
if (!subscriber.isUnsubscribed()) {
try {
subscriber.onError(e);
} catch (Throwable t) {
if (!(t instanceof OnErrorNotImplementedException)) {
throw t;
}
}
}
}
#Override
public void onNext(T t) {
if (!isUnsubscribed()) {
subscriber.onNext(t);
}
}
};
}
});
The problem with this if observeOn() is called later then this will be asynchronous and obviously my exception handling here will not work.
Is there way to achieve this. I wish there would be a subscribe() method which does not throw OnErrorNotImplementedException in onError.
Here is another possible solution, you can define the onNext and a Throwable (also you cannot loose the lambda syntax):
.subscribe(t -> doSomething(t), e -> showError(e));
here's how we do it at work. Instead of making actions we made an abstract NYTSubscriber which has onError and onCompleted implemented. This way you can use this subscriber and only implement the onNext callback OR you can override onError and onCompleted when necessary
public abstract class NYTSubscriber<T> extends Subscriber<T> {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
}
If you are using Kotlin then the syntax is like
.subscribe({success -> doOnSuccess(success)},{error -> doOnError(error)})
When you do like this '.onErrorResumeNext(Observable.empty())' your stream will be completed when error occurs - that's why your actions is never called.
You can user '.retry()' and your stream will be restarted automatically when you have an error.
This Error comes when calling the Subscribe method, but not providing onError() callbacks.
Kotlin
subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
// on successful completion }
override fun onError(e: Throwable) {
//error message
}
}
)

Categories

Resources