Realm.getInstance(context)
will rarely return an already closed realm instance. How is this possible?
I am using Realm with RxJava, per https://realm.io/news/using-realm-with-rxjava/
In particular, this method throws an IllegalStateException: This Realm instance has already been closed, making it unusable.
@Override
public void call(final Subscriber<? super RealmList<T>> subscriber) {
final Realm realm = Realm.getInstance(context);
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
try {
realm.close();
} catch (RealmException ex) {
subscriber.onError(ex);
}
}
}));
RealmList<T> object;
realm.beginTransaction(); //THROWS EXCEPTION
//...
}
If I comment out the realm.close();
issue, no problems. Though I think this will lead into a native memory leak, then.
My best guess as to why this is occurring is that multiple calls to this method are being made, and if these method calls line up perfectly, then an already closed realm instance can be retrieved?
EDIT: By using Schedulers.io()
, I get a lot of Calling close() on a Realm that is already closed
warnings. My guess here is that somehow after I am done using the .io()
thread, the realm instance is automatically closed. Not sure why this would happen though.
EDIT2: By switching to using Schedulers.newThread()
instead of Schedulers.io()
for my observables, the issue stopped appearing. But I am seeing a lot of Remember to call close() on all Realm instances
warnings. I am pretty sure I am closing them, so I am very confused about this.
EDIT3: By switching to using AndroidSchedulers.mainThread()
, no errors. Except my Realm calls run on the main thread, which is bad bad bad. My guess why this causes no warnings is because the realm
now lives on the main thread, which is also where realm.close()
is called (via the rx.subscriber
).
EDIT4: Here's the logic for my realm observable call.
@Override
public Observable<List<ImageArticleCategoryEntity>> getArticleBuckets() {
return RealmObservable.list(context, GET_ARTICLE_BUCKETS)
.filter(FILTER_OUT_NULL_OR_EMPTY_LIST)
.switchIfEmpty(refreshAndSaveAndLoadNewDataFromDb)
.map(CONVERT_FROM_REALMLIST_TO_IMAGE_ARTICLE_ENTITYLIST);
}
public void loadArticleImages() {
articleRepo.getArticleBuckets()
.subscribeOn(RealmThread.get())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<ImageArticleCategoryEntity>>() {
@Override
public void onCompleted() {
Timber.v("Loading article images complete!");
if (view != null)
view.hideLoadingAnimation();
}
@Override
public void onError(Throwable e) {
Timber.e("Error loading article images", e);
Log.e("tag", "Error loading article images", e);
if (view != null) {
view.hideLoadingAnimation();
view.showLoadingErrorToast();
}
}
@Override
public void onNext(List<ImageArticleCategoryEntity> integerImageArticleCategoryEntityHashMap) {
if (view != null)
view.loadArticleImages(integerImageArticleCategoryEntityHashMap);
}
});
Let's simplify how the lifecycle of the Realm
instance is managed. If we manage it in the context of the resume/pause cycle of an Activity
or Fragment
, we can much more easily control and stop work that might consume that Realm
instance. The tools in RxAndroid help a lot with that.
So, I'm going to assume for this example this is happening from an Activity
, but a very similar approach could be used from a Fragment
, or extracted into helper classes with just a hint more plumbing.
If you bind your Observable
to the lifecycle of the Activity
or Fragment
using RxAndroid, which it appears you are already using for mainThread()
, you should be able to manage your Realm
instance easily within a lifecycle. I'm also using RxAsyncUtil for Async.toAsync
, which makes the original creation of the Realm
instance easier.
I've used Rx a lot more than Realm (though I've toyed with it a good bit), so forgive me if some of my API usage isn't perfect. I've also abbreviated things to java8-style lambdas just for ease-of-writing and reading. If you're not using something like retrolambda it should still be pretty easy to convert it back.
class MyActivity extends Activity {
private final CompositeSubscriptions realmSubscriptions = new CompositeSubscription();
private AsyncSubject<Realm> realm;
@Override
protected void onResume() {
super.onResume();
realm = AsyncSubject.create();
realmSubscriptions.add(getRealmInstance());
// This could actually happen anytime between onResume and onPause.
realmSubscriptions.add(loadArticlesImages());
}
private <T> Observable<T> bindToRealm(final Observable<T> observable) {
// Utility to bind to the activity lifecycle, observe on the main thread
// (implicit in the bindActivity call), and do work on the Realm thread.
return AppObservable.bindActivity(this, observable.subscribeOn(RealmThread.get()));
}
private Observable<List<ImageArticleCategoryEntity>> getArticleBuckets(
final Realm realm) {
/* As is, except the realm instance should be passed to RealmObservable.list instead
of the context. */
}
private Subscription getRealmInstance() {
// Grab the realm instance on the realm thread, while caching it in our AsyncSubject.
return bindtoRealm(Async.toAsync(() -> Realm.getInstance(MyActivity.this)))
.subscribe(realm);
}
private Subscription loadArticleImages() {
// Using the flatMap lets us defer this execution until the
// Realm instance comes back from being created on the Realm thread.
// Since it is in an AsyncSubject, it will be available nearly
// immediately once it has been created, and is cached for any future
// subscribers.
return bindToRealm(realm.flatMap((realm) ->
articleRepo.getArticleBuckets(realm).subscribeOn(RealmThread.get())))
.subscribe(
(next) -> {
if (view != null) {
view.loadArticleImages(next);
}
},
(error) -> {
Timber.e("Error loading article images", e);
Log.e("tag", "Error loading article images", e);
if (view != null) {
view.hideLoadingAnimation();
view.showLoadingErrorToast();
}
},
// onCompleted
() -> {
Timber.v("Loading article images complete!");
if (view != null) view.hideLoadingAnimation();
});
}
@Override
protected void onPause() {
// Stop any work which we added that involves the Realm instance.
realmSubscriptions.clear();
// Clean up the AsyncObservable. If it has a Realm instance, close it.
if (realm.getValue() != null) {
realm.getValue().close();
}
realm.dispose();
realm = null;
super.onPause();
}
}
You should be able to easily extract this outside of an activity as needed, and just pass an Activity/Fragment instance for the lifecycle binding, as well as the Observable<Realm>
which in this case would be the AsyncSubject
. If there are still race conditions due to the subscription work, you may want to experiment with adding .unsubscribeOn(AndroidSchedulers.mainThread())
or even .unsubscribeOn(Schedulers.immediate())
(I'm actually not sure which would be best in this scenario) to bindToRealm
to ensure unsubscribing happens when you want it to in the onPause
, before the Realm
instance is closed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With