Rxjava + Realm access from incorrect thread - android

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.

Related

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 Fetch from Api if cache expired

I need to query data from Api then save it to Realm object. I need to get the data(observable) to the Presenter from the realm object unless 5 minutes from the last Api query elapsed, in other case I need to fetch from Api again. I`m new to RxJava. Any suggestions?
You could create a class hosting a ReplaySubject and some update logic:
class TimedCache<T> {
final Subject<T> cache =
ReplaySubject.createWithTime(5, TimeUnit.MINUTES).toSerialized();
final Single<T> valueProvider;
TimedCache(Single<T> valueProvider) {
this.valueProvider = valueProvider;
}
public Observable<T> valueObservable() {
return cache.take(1)
.switchIfEmpty(
valueProvider
.doOnSuccess(v -> {
cache.onNext(v);
// update realm here
})
.toObservable()
);
}
}
How about setting up two different observables:
1.) for observing the Realm data:
realm.where(MyData.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded)
.subscribe(...);
2.) for fetching the data every 5 minutes
Observable.interval(5, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.switchMap((ignored) -> {
return apiService.getData();
})
.subscribe((data) -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction((r) -> {
r.insertOrUpdate(data);
});
}
});
EDIT: then just call a method like
flowable = realm.where(MyData.class)
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded)
.subscribe(...);
if(cache.isOlderThanFiveMinutes()) {
startRefreshTaskOnBackgroundThread();
}

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

Realm Proper way of Copy Object In Transaction with RxJava

Currently, when copying a value to realm, i do the following:
public void addToRealm(Home item, RealmChangeListener<E> listener) {
realm.executeTransaction((Realm realm1) ->
realm1.copyToRealm(item).addChangeListener<Home>(listener));
}
And then i can access the newly added object inside the listener. What is the proper RxJava way of accomplishing the same? The observable must return
Observable<Home>, which is the realmCopy not the original object. Can any1 please provide a sample?
Managed to get it working by doing this, altought im not sure it is the best approach... What is the recommended approach?
return Observable.just(homeItem)
.map { (HomeItem homeItem) ->
return AnotherHomeItem(homeItem.xxx, homeItem.yyy)
}
.flatMap { (AnotherHomeItem anotherItem) ->
realm.beginTransaction()
val newItem = realm.copyToRealm(anotherItem).asObservable< AnotherHomeItem >()
realm.commitTransaction()
return newItem
}
.filter {
return it.isLoaded
}
You should write to the Realm on a background thread, and observe with a different subscription on the UI thread.
You persist with one subscription on the background thread:
public Subscription downloadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}
And you read with asObservable() on the UI thread:
public Subscription readFromRealm() {
return realm.where(SomeObject.class)
.findAllAsync()
.asObservable()
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.filter(RealmResults::isLoaded)
.subscribe(objects -> adapter.updateData(objects));
}
Using Realm with RxJava
For queries, Realm provides the realmResults.asObservable() method. Observing results is only possible on looper threads (typically the UI thread).
For this to work, your configuration must contain the following
realmConfiguration = new RealmConfiguration.Builder(context) //
.rxFactory(new RealmObservableFactory()) //
//...
.build();
Afterwards, you can use your results as an observable.
Observable<RealmResults<SomeObject>> observable = results.asObservable();
For asynchronous queries, you should filter the results by isLoaded(), so that you receive an event only when the query has been executed. This filter() is not needed for synchronous queries (isLoaded() always returns true on sync queries).
Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence ->
realm.where(SomeObject.class)
.contains("searchField", charSequence.toString(), Case.INSENSITIVE)
.findAllAsync()
.asObservable())
.filter(RealmResults::isLoaded) //
.subscribe(objects -> adapter.updateData(objects));
For writes, you should either use the executeTransactionAsync() method, or open a Realm instance on the background thread, execute the transaction synchronously, then close the Realm instance.
public Subscription loadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}

Correct flow in RxJava with Retrofit and Realm

I'm implementing network API with the combination of RxJava and Retrofit, and I use Realm as my database. I got it pretty much working but I'm wondering if it is the correct approach and flow of events. So, here is the RetrofitApiManager.
public class RetrofitApiManager {
private static final String BASE_URL = "***";
private final ShopApi shopApi;
public RetrofitApiManager(OkHttpClient okHttpClient) {
// GSON INITIALIZATION
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.build();
shopApi = retrofit.create(ShopApi.class);
}
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
realm.close();
})
.flatMap(response -> {
Realm realm = Realm.getDefaultInstance();
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
realm.close();
return results;
});
}
}
And here is the call to get RealmResults<Shop> inside a Fragment.
realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded)
.first()
.flatMap(shops ->
shops.isEmpty() ? retrofitApiManager.getShops() : Observable.just(shops))
.subscribe(
shops -> initRecyclerView(),
throwable -> processError(throwable));
Here are my questions:
Is it a correct approach to chain events like in the example above or should I manage them in a different way?
Is it OK to useRealm instance in getShops() method and close i there or would it be better to pass it as an argument and then manage it somehow? Although, this idea seems to be a bit problematic with threads and calling Realm.close() always at the right time.
1) I would try to do as much as possible on the background thread, right now you are doing a lot of the work on the UI thread.
2)
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.insertOrUpdate(response.shops));
} // auto-close
})
.flatMap(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
} // auto-close
return results;
});
}
All Realm data is lazy-loaded, so it is only available while the Realm instance is open, so closing it after retrieving it has a high chance of not working. In your case though you are flat-mapping on the main thread, so most likely there is already an open instance there.
If you want you can use copyFromRealm() to get unmanaged data out that can be moved across threads and are not connected to Realm anymore, but they will also loose their live update features and take up more memory.
It would probably do this instead:
public Observable<RealmResults<Shop>> getShops() {
return shopApi.getShops()
.subscribeOn(Schedulers.io())
.doOnNext(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(response.shops));
} // auto-close
})
.observeOn(AndroidSchedulers.mainThread())
.flatMap(response -> {
Observable<RealmResults<Shop>> results = realm.where(Shop.class)
.findAllAsync()
.asObservable()
.filter(RealmResults::isLoaded);
return results;
});
Alternatively you can treat the network request as a side-effect and just depend on Realm notifying you when there is changes (better approach IMO as you separate network from DB access which is e.g. what the Repository pattern is about)
public Observable<RealmResults<Shop>> getShops() {
// Realm will automatically notify this observable whenever data is saved from the network
return realm.where(Shop.class).findAllAsync().asObservable()
.filter(RealmResults::isLoaded)
.doOnNext(results -> {
if (results.size() == 0) {
loadShopsFromNetwork();
}
});
}
private void loadShopsFromNetwork() {
shopApi.getShops()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(r -> r.insertOrUpdate(response.shops));
} // auto-close
});
}
What Christian Melchior mentioned in his answer, makes perfect sense, and should solve the problem you are having at your hand, but down the line this approach may introduce other issue(s).
In a good architecture, all the major modules(or libraries) should be isolated from rest of the code. Since Realm, RealmObject or RealmResult can not be passed across threads it is even more important to make Realm & Realm related operations isolated from rest of the code.
For each of your jsonModel class, you should have a realmModel class and a DAO (Data Access Object). Idea here is that other than DAO class none of the class must know or access realmModel or Realm. DAO class takes jsonModel, converts to realmModel, performs read/write/edit/remove operations, for read operations DAO converts realmModel to jsonModel and returns with it.
This way it is easy to maintain Realm, avoid all Thread related issues, easy to test and debug.
Here is an article about Realm best practices with a good architechture https://medium.com/#Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
Also a sample project demonstrating Integration of Realm on Android with MVP(Model View Presenter), RxJava, Retrofit, Dagger, Annotations & Testing. https://github.com/viraj49/Realm_android-injection-rx-test
In my case, I seem to have defined a query for the RealmRecyclerViewAdapter like this:
recyclerView.setAdapter(new CatAdapter(getContext(),
realm.where(Cat.class).findAllSortedAsync(CatFields.RANK, Sort.ASCENDING)));
And otherwise defined a condition for Retrofit with RxJava to download more stuff when the condition is met:
Subscription downloadCats = Observable.create(new RecyclerViewScrollBottomOnSubscribe(recyclerView))
.filter(isScrollEvent -> isScrollEvent || realm.where(Cat.class).count() <= 0)
.switchMap(isScrollEvent -> catService.getCats().subscribeOn(Schedulers.io())) // RETROFIT
.retry()
.subscribe(catsBO -> {
try(Realm outRealm = Realm.getDefaultInstance()) {
outRealm.executeTransaction((realm) -> {
Cat defaultCat = new Cat();
long rank;
if(realm.where(Cat.class).count() > 0) {
rank = realm.where(Cat.class).max(Cat.Fields.RANK.getField()).longValue();
} else {
rank = 0;
}
for(CatBO catBO : catsBO.getCats()) {
defaultCat.setId(catBO.getId());
defaultCat.setRank(++rank);
defaultCat.setSourceUrl(catBO.getSourceUrl());
defaultCat.setUrl(catBO.getUrl());
realm.insertOrUpdate(defaultCat);
}
});
}
}, throwable -> {
Log.e(TAG, "An error occurred", throwable);
});
And this is for example a search based on an edit text's input:
Subscription filterDogs = RxTextView.textChanges(editText)
.switchMap((charSequence) ->
realm.where(Dog.class)
.contains(DogFields.NAME, charSequence.toString())
.findAllAsyncSorted(DogFields.NAME, Sort.ASCENDING)
.asObservable())
.filter(RealmResults::isLoaded)
.subscribe(dogs -> realmRecyclerAdapter.updateData(dogs));

Categories

Resources