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.
Related
I'm trying to implement the same behavior of how Flow with Room Database in which it already listens to changes. I am pretty confused about how to use the RealmInstance.toflow() in which it returns a Flow<Realm>, however, I don't want that. If we compare it to Room Database, you can already specify what return type you it to be(ex. Flow<Entity>. Since Realm doesn't have any kind of DAOs, I am currently left with using
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
}
I don't know how to integrate the code above in my repository since you cant emit inside the addChangeListener because it needs a coroutine however,i don't want a solution of having to create a Global coroutine. I currently have this on my ItemRepository:
override suspend fun getItems(): Flow<Resource<List<Item>>> = flow{
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
//You cant emit() here since it needs a coroutine
}
}
The bottom line problem is: that I want to listen to changes in the realm in which my repository already returns Flow of the Object I want. Something like how Room DB and Flows work.
When you want to convert a callback listener like Realm changeListener to a Coroutine, you have two options :
One- Using CallBackFlow :
return callbackFlow<Location> {
RealmInstance.addChangeListener{
realm->
trySend(realm.toString()) // for example to return a string
}
addOnFailureListener { e ->
close(e)
}
awaitClose {
// remove and close your resources
}
}
TWO- Use suspendCancellableCoroutine
return suspendCancellableCoroutine { continuation ->
RealmInstance.addChangeListener{
realm->
//Handle DB Changes here
continuation.resume(returnValue)
}
continuation.invokeOnCancellation {
// do cleanup
}
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()
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.
I recently faced a problem in which i had in memory a RealResult List of objects and was showing it in a view. Upon user click, the current showing item should be marked as deleted in realm (property isDeleted)
So i was just getting that object from the lazy RealmResults list, open a transaction and mark it deleted. As the RealmResults are auto-updated, i had a change listener bound to a notifityDataSetChanged. Everything works fine except this warning:
Mixing asynchronous queries with local writes should be avoided. Realm will convert any async queries to synchronous in order to remain consistent. Use asynchronous writes instead
Which is problematic because my list is enormous and i don't want the query to become sync. I solved it this way, which i don't know it it is right. Instead of giving the item object to the update function, i give the id of the object, and then do this:
Realm.getDefaultInstance().use { realm ->
realm.executeTransactionAsync {
// find the item
realm.where(ItemRealm::class.java)
.equalTo(ItemRealm.ID, itemId).findFirstAsync()
.addChangeListener(object : RealmChangeListener<ItemRealm> {
override fun onChange(element: ItemRealm) {
element.deleted = true
element.removeChangeListener(this)
}
})
}
}
The problem which im unsure is the async part of the query inside an async transaction.
Edit. Actually, it throws java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
Edit2: Tried this and show Realm access on different thread:
fun setItemDeleted(itemId: Long) {
// write so new realm instance
Realm.getDefaultInstance().use { realm ->
realm.executeTransactionAsync {
// find the item
val item = realm.where(ItemRealm::class.java)
.equalTo(ItemRealm.TIMESTAMP, itemId).findFirst()
item?.userDeleted = true
}
}
}
Everything in executeTransactionAsync() runs on a background thread, so you should use the synchronous methods to obtain your object inside it.
Realm.getDefaultInstance().use { realm ->
realm.executeTransactionAsync { bgRealm ->
// find the item
val item = bgRealm.where(ItemRealm::class.java)
.equalTo(ItemRealm.ID, itemId).findFirst()
item?.deleted = true
}
}
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.