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

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

Related

RxJava2 Android Schedulers.io() and Schedulers.newThread()

I'm using RxJava2 Android Networking for network call. The problem I'm facing is when I'm trying to hit the API through Schedulers.io() sometimes it does not give any response whereas when I tried hitting the API with Schedulers.newThread() it always gives a response
.subscribeOn(Schedulers.newThread()) AND .subscribeOn(Schedulers.io())
I think there are a lot of reasons if you didn't receive a response from the beck-end server.
Bellow i will post-you some example code for making network-call via rxJava2.
Note that the job is executed via Schedulers.io() and result is observerd on the main-thread. Note that getCoinList() should return some observable
service.getCoinList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<CoinList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CoinList coinList) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});

Realm access from incorrect thread in rx and dagger

I know this question asks a lot but i am confused by reading this question.In my project i am using dagger, rx and realm in mvp pattern.I have an application with a LoginActivity that when the user login correctly, the user information that comes from server store in realm in this page and user jumps to MainActivity.In MainActivity i want this user info but i got this error:
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
I know that :
Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
In my Appliction class i have initialized DaggerAplicationComponnet.I have applicationModule:
#AppScope
#Component(modules = {ApplicationModule.class, NetworkModule.class , AppRealmModule.class})
public interface ApplicationComponent {
Realm getRealm();
...
}
and AppRealmModule:
#Module
public class AppRealmModule {
#Provides
#AppScope
Realm provideRealm (Context context) {
Realm.init(context);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.name("Payeshdb")
.build();
Realm.setDefaultConfiguration(realmConfiguration);
return Realm.getDefaultInstance();
}
That means , i create realm instance on ui thread.Now in MainActivity i have read realm:
#Override
public Observable<User> findUser() {
return Observable.create(new ObservableOnSubscribe<User>() {
#Override
public void subscribe(ObservableEmitter<User> emitter) throws Exception {
try {
User user = realm.where(User.class).findFirst();
User user1 = realm.copyFromRealm(user);
emitter.onNext(user1);
emitter.onComplete();
}catch (Exception e) {
emitter.onError(e);
}
}
});
}
Now, in presenter i am using rx in this way:
userRepository.findUser()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<User>() {
#Override
public void onNext(User user) {
}
#Override
public void onError(Throwable e) {
// got error: Realm access from incorrect thread.
}
#Override
public void onComplete() {
}
});
this is a place that i got above error. In some post i have read this:
Use copyFromRealm to get the data from realm which will return them as plain objects rather than realm objects.
And i do that but i still got error.
Is it possible initialize realm with dagger and use different thread for crud operation?
What is your suggestion?
#Override
public Observable<User> findUser() {
return Observable.create(new ObservableOnSubscribe<User>() {
#Override
public void subscribe(ObservableEmitter<User> emitter) throws Exception {
try(Realm realm = Realm.getDefaultInstance()) {
realm.refresh();
User user = realm.where(User.class).findFirst();
User user1 = realm.copyFromRealm(user);
emitter.onNext(user1);
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
});
}
As the error says
Realm objects can only be accessed on the thread they were created
So just subscribe on the same thread you created Realm
userRepository.findUser()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())

Rxjava + Realm access from incorrect thread

I'm getting this exception reading/writing from Realm
06-19 09:49:26.352 11404-11404/****** E/ContentValues: loadData: OnError Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:385)
at io.realm.RealmResults.isLoaded(RealmResults.java:115)
at io.realm.OrderedRealmCollectionImpl.size(OrderedRealmCollectionImpl.java:307)
at io.realm.RealmResults.size(RealmResults.java:60)
at java.util.AbstractCollection.isEmpty(AbstractCollection.java:86)
at /****** .lambda$loadData$0(SplashPresenter.java:42)
at /****** $$Lambda$1.test(Unknown Source)
at io.reactivex.internal.operators.observable.ObservableFilter$FilterObserver.onNext(ObservableFilter.java:45)
at io.reactivex.observers.SerializedObserver.onNext(SerializedObserver.java:111)
at io.reactivex.internal.operators.observable.ObservableDelay$DelayObserver$1.run(ObservableDelay.java:84)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is the code:
mSubscribe = Observable.just(readData())
.delay(DELAY, TimeUnit.SECONDS)
.filter(value -> !value.isEmpty())
.switchIfEmpty(createRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
getView().hideLoading();
writeData(data);
},
(throwable -> {
}));
Read data
private List<CategoryModel> readData() {
Realm defaultInstance = Realm.getDefaultInstance();
List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");
defaultInstance.close();
return title;
}
Write data
private void writeData(List<CategoryModel> categoryModels) {
try {
Realm defaultInstance = Realm.getDefaultInstance();
defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
defaultInstance.close();
} finally {
getView().notifyActivity(categoryModels);
}
}
How can I follow this logic using the proper threads?
The only rule to using Realm across threads is to remember that Realm, RealmObject or RealmResults instances cannot be passed across threads.
When you want to access the same data from a different thread, you should simply obtain a new Realm instance (i.e. Realm.getDefaultInstance()) and get your objects through a query (then close Realm at the end of the thread).
The objects will map to the same data on disk, and will be readable & writeable from any thread! You can also run your code on a background thread using realm.executeTransactionAsync() like this
.
How can i follow this logic using the proper threads?
By not trying to read on Schedulers.io() for your UI thread (Realm gives auto-updating lazy-loaded proxy views that provide change notifications for your data on the UI thread, after all).
So instead of this
mSubscribe = Observable.just(readData())
.delay(DELAY, TimeUnit.SECONDS)
.filter(value -> !value.isEmpty())
.switchIfEmpty(createRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
getView().hideLoading();
writeData(data);
},
(throwable -> {
}));
private List<CategoryModel> readData() {
Realm defaultInstance = Realm.getDefaultInstance();
List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");
defaultInstance.close();
return title;
}
private void writeData(List<CategoryModel> categoryModels) {
try {
Realm defaultInstance = Realm.getDefaultInstance();
defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
defaultInstance.close();
} finally {
getView().notifyActivity(categoryModels);
}
}
You're supposed to have something like
private Observable<List<CategoryModel>> readData() { // Flowable with LATEST might be better.
return io.reactivex.Observable.create(new ObservableOnSubscribe<List<CategoryModel>>() {
#Override
public void subscribe(ObservableEmitter<List<CategoryModel>> emitter)
throws Exception {
final Realm observableRealm = Realm.getDefaultInstance();
final RealmResults<CategoryModel> results = observableRealm.where(CategoryModel.class).findAllSortedAsync("title");
final RealmChangeListener<RealmResults<CategoryModel>> listener = results -> {
if(!emitter.isDisposed() && results.isLoaded()) {
emitter.onNext(results);
}
};
emitter.setDisposable(Disposables.fromRunnable(() -> {
if(results.isValid()) {
results.removeChangeListener(listener);
}
observableRealm.close();
}));
results.addChangeListener(listener);
}
}).subscribeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread());
}
private void setSubscription() {
mSubscribe = readData()
.doOnNext((list) -> {
if(list.isEmpty()) {
Single.fromCallable(() -> this::createRequest)
.subscribeOn(Schedulers.io())
.subscribe((data) -> {
writeData(data);
});
}
}).subscribe(data -> {
if(!data.isEmpty()) {
getView().hideLoading();
getView().notifyActivity(data);
}
}, throwable -> {
throwable.printStackTrace();
});
}
private void writeData(List<CategoryModel> categoryModels) {
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(realm -> realm.insertOrUpdate(categoryModels));
}
}
void unsubscribe() {
mSubscribe.dispose();
mSubscribe = null;
}
This way (if I didn't mess anything up), you end up with the reactive data layer described here and here, except without the additional overhead of mapping out the entire results.
EDIT:
Since Realm 4.0, it is possible to expose a RealmResults directly as a Flowable (on the UI thread, or background looper thread).
public Flowable<List<MyObject>> getLiveResults() {
try(Realm realm = Realm.getDefaultInstance()) {
return realm.where(MyObject.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded);
}
}
You need to extract required data from realm objects into POJO and emit POJOs using map operator, so that view objects can updated with data from realm using pojo on android main thread.
You can only manipulate Realm objects in a transaction or only in the thread you read/write these objects. In your case you are getting a RealmResult from the readData method and using RxJava you are switching threads which caused the exception. Use copyFromRealm to get the data from realm which will return them as plain objects rather than realm objects.

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.

RxAndroid: Is there a clean way to get a Subscriber object from an Observable?

So from my view model I call my Observable in another class:
getAuthentication.kickoff()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<RxOkHttpResponse>() {
#Override
public final void onCompleted( ) {
getAlbums();
}
#Override
public final void onError(Throwable e) {
userMsgHandler.showToast(mParent,mParent.getString(R.string.error_cannot_authenticate));
}
#Override
public final void onNext(RxOkHttpResponse response) {
mSubscription = response.subscription;
}
});
So this call obviously returns an Observable. So I'm wondering what the cleanest way to call subscriber.unsubscribe() on this Observable that is returned or if there even is a way (Right now I return it in onNext -- but I'm not happy bout that)
So is there a way to format the code to store .subscribe() in a member variable:
.observeOn(AndroidSchedulers.mainThread())
mSubscriber = .subscribe(new Subscriber<RxOkHttpResponse>() {
...
I'm using v 1.1.0
The answer depends on whether you're using RxJava 1 or 2. In case of RxJava 1 subscribe() will return Subscription instance which you can then call unsubscribe() on. However for RxJava 2 I believe you need to add onSubscribe()
This is the syntax I was looking for:
mSubscriber = ( getAuthentication.kickoff()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
).subscribe(new Subscriber<RxOkHttpResponse>() {
...
Now I can easily store mSubscriber in my view model for clean up triggered by OnDestroy() in my Activity

Categories

Resources