Realm access from incorrect thread on compositeDisposable clear - android

I am adding all my observer that are subscribed in an activity are added to CompositeDisposable.
OnStop of activity is calling mCompositeDisposable.clear() which creates below crash log.
As CompositeDisposable.clear calls onDispose please find below code does to realm
Single.create(...).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
mCompositeDisposable.add(observer);
creats realm here in Schedulers.io()
Single<RealmList<T>> source ...
source .doOnDispose(() -> {
if (mRealm == null) {
return;
}
if (Looper.myLooper() != null) {
mRealm.removeAllChangeListeners();
}
if (!mRealm.isClosed()) {
mRealm.close();
}
mRealm = null;
}
Giving the below crash logs
java.lang.IllegalStateException: Realm access from incorrect thread.
Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.JN(SourceFile:438)
at io.realm.BaseRealm.removeAllListeners(SourceFile:263)
at io.realm.Realm.removeAllChangeListeners(SourceFile:1399)
...
at io.reactivex.internal.operators.single.SingleDoOnDispose$DoOnDisposeObserver.dispose(SourceFile:60)
at io.reactivex.internal.operators.single.SingleDoFinally$DoFinallyObserver.dispose(SourceFile:85)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.dispose(SourceFile:78)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.dispose(SourceFile:78)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.dispose(SourceFile:87)
at io.reactivex.internal.disposables.DisposableHelper.aq(SourceFile:124)
at io.reactivex.observers.DisposableObserver.dispose(SourceFile:91)
at io.reactivex.disposables.CompositeDisposable.a(SourceFile:240)
at io.reactivex.disposables.CompositeDisposable.clear(SourceFile:206)
Tried with .unsubscribeOn(Schedulers.io()) but didnt worked

creats realm here in Schedulers.io()
You're creating instance of Realm in mainThread then asking Realm to operate (e.g. load data) in io thread which is impossible in Realm by design. Example by developers of Realm states few points to keep in mind here https://github.com/realm/realm-java/tree/master/examples/rxJavaExample.
Implementing 1st and 4th points into your code snippet we will get us proper solution. Note that method findAllAsync() will do everything asynchronously for you no need to create io thread by using RxJava
Single<RealmList<T>> realmResults = realm.where(....)
.equalTo(...)
.findAllAsync()
.asFlowable()
.map { realmResult->
val realmList = RealmList<T>()
realmList.addAll(realmResult.toList())
return realmList
}
.firstOrError()
realm.close()

Related

Realm crashes after realm.close()

I have the following code:
val realm = Realm.getDefaultInstance()
return realm.use { realm ->
realm.where(BookItem::class.java).findAll()
}
I obtain the following error:
java.lang.IllegalStateException: This Realm instance has already been
closed, making it unusable.
I supose that is because the where function didn't finished when close happens. How should I do this instead?
read the documentation on how to properly close realm documentation
val realm = Realm.getDefaultInstance();
try {
// ... Do something ...
} finally {
realm.close();
}
I supose that is because the where function didn't finished when close happens. How should I do this instead? no since findAll() is not async. maybe you have a code that call the realm instance.

Perform work on a different thread

I execute a Realm query,
then I need to perform a time consuming mapping of the result of the Realm query, consequently it needs to be done on a worker thread.
Finally I need the results on the main thread (because they update the UI).
But the following code (understandably) gives me an exception: "Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created."
val defaultInstance = Realm.getDefaultInstance()
val subscription = defaultInstance
.where(Bar::class.java)
.equalTo("foo", true)
.findAllAsync()
.asObservable()
.filter { it.isLoaded && it.isValid }
.map { defaultInstance.copyFromRealm(it) }
// The following is a time consuming task, I need to perform it on another thread
.observeOn(workerScheduler)
.map { someComplexMapping(it) }
// but the results are needed on the main thread (because it updates the UI)
.subscribeOn(mainThreadScheduler)
.subscribe(observer)
Please how do I achieve what I need?
val defaultInstance = Realm.getDefaultInstance()
val subscription = defaultInstance
.where(Bar::class.java)
.equalTo("foo", true)
.findAllAsync()
.asObservable()
.subscribeOn(mainThreadScheduler)
.filter { it.isLoaded && it.isValid }
.observeOn(Schedulers.io())
.map {
Realm.getDefaultInstance().use {
//it.refresh() // shouldn't be needed
it.copyFromRealm(it.where(Bar::class.java).equalTo("foo", true).findAll())
}
}
// The following is a time consuming task, I need to perform it on another thread
.observeOn(workerScheduler)
.map { someComplexMapping(it) }
// but the results are needed on the main thread (because it updates the UI)
.observeOn(mainThreadScheduler)
.subscribe(observer)
Create an Observable before the Realm query
and put it into the UI thread with .observeOn(AndroidScheduler.mainThread()). Run the Realm query and the other stuffs like you did before.

Android Kotlin Realm Proper Way of Query+ Return Unmanaged Items on Bg Thread

What is the proper way of querying and returning an unmanaged result of items with realm, everything in the background thread?. I'm using somethibf like this:
return Observable.just(1)
.subscribeOn(Schedulers.io())
.map {
val realm = Realm.getDefaultInstance()
val results = realm.where(ItemRealm::class.java)
.equalTo("sent", false).findAll()
realm to results
}
.map {
val (realm, results) = it
val unManagedResults = realm.copyFromRealm(results)
realm.close()
unManagedResults
}
}
And then chaining this observable with another one that will post the results to a server.
The solution working, although is a bit ugly on this aspects:
No proper way of wrapping the realmQuery in an observable, because
there is no way of opening a realInstance in a background thread without this kind of cheat (at least that i know about), so i need to use this fake
observable Observable.just(1).
Not the best place to open and close Realm instances, inside first and second map
I don't know if it is guaranteed that the realm instance is closed after all the items have been copied.
So what is the proper way of Query and Return unmanaged results on the background thread (some context, i need this to send the results to a server, in the background and as this task is totally independent from my app current data flow, so it should be off the main thread).
Suggested Version:
return Observable.fromCallable {
Realm.getDefaultInstance().use { realm ->
realm.copyFromRealm(
realm.where(ItemRealm::class.java)
.equalTo(ItemRealm.FIELD_SEND, false).findAll()
)
}
}
This is how you would turn your Realm objects unmanaged:
return Observable.defer(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
return Observable.just(
realm.copyFromRealm(
realm.where(ItemRealm.class).equalTo("sent", false).findAll()
)
);
}
}).subscribeOn(Schedulers.io());
Although this answer is Java, the Kotlin answer is just half step away.

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

How to use Realm asObservable with RxJava's concat() operator?

I'm trying to use Realm with RxJava and Retrofit in a way DanLew described here concating input from realm and retrofit but it gets stuck if I adding realm into the chain
Observable.concat(countryStorage.restoreAsObservable(),
networkService.api()
.getCountries()
.doOnNext(countryStorage::save))
.first()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//never reaching here)
storage
#Override public Observable<List<Country>> restoreAsObservable() {
Realm realm = realmProvider.get();
return realm.where(Country.class)
.findAll()
.asObservable()
.map(countries -> return realm.copyFromRealm(countries))
.first(countries -> return !countries.isEmpty())
.doOnCompleted(realm::close());
}
It seems that this could happen that observable is hot from Realm, but nothing about it in the docs and how I suppose to compose Realm with other observables?
UPDATE:
It turns to be that it works fine in old way. The question still remain about new api.
return Observable.just(
realm.copyFromRealm(realm.where(Country.class).findAll()))
.filter(countries -> !countries.isEmpty())
.doOnCompleted(realm::close);
It is happening because countryStorage.restoreAsObservable() never completes and if you read concat doc, it explicitly states that:
Concat waits to subscribe to each additional Observable that you pass to it until the previous Observable completes.
Instead you can just do something like:
countryStorage.restoreAsObservable()
.doOnSubscribe(() -> {
networkService.api()
.getCountries()
.subscribe(countryStorage::save)
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//do smth)

Categories

Resources