Android: Realm.getInstance (context) returns an already closed instance of the area

Realm.getInstance(context) rarely returns an already closed instance. How is this possible?

I am using Realm with RxJava, for 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 on the problem realm.close(); , no problem. Although I think this will lead to a memory leak from memory.

My best guess as to why this happens is that multiple calls to this method are made, and if these calls to the methods line up perfectly, can an instance of an already closed state be restored?

EDIT: using Schedulers.io() , I get many warnings Calling close() on a Realm that is already closed . I assume that after I have finished using the .io() stream, the region instance automatically closes. Not sure why this will happen.

EDIT2: By switching to using Schedulers.newThread() instead of Schedulers.io() for my observables, the problem stopped appearing. But I see many warnings Remember to call close() on all Realm instances . I am sure that I am closing them, so I am very confused about this.

EDIT3: Switching to using AndroidSchedulers.mainThread() , no errors. Also, my Realm calls are triggered on the main thread, which is bad bad. My guess why this does not raise any warnings is that realm now lives in the main thread, which is also called by realm.close() (via rx.subscriber ).

EDIT4: Here is the logic of observing my kingdom.

 @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); } }); 
+6
source share
1 answer

Simplify how the life cycle of a Realm instance is managed. If we manage it in the context of the resume / pause Activity or Fragment cycle, we can control and stop the work that this Realm instance can consume much easier. Tools in RxAndroid help a lot with this.

So, I'm going to assume for this example that this comes from Activity , but a very similar approach can be used from Fragment or extracted into helper classes, with only a hint of more plumbing.

If you bind an Observable to an Activity or Fragment lifecycle using RxAndroid, which it seems you are already using for mainThread() , you can easily manage your Realm instance lifecycle. I also use RxAsyncUtil for Async.toAsync , which facilitates the initial creation of a Realm instance.

I used Rx a lot more than Realm (although I played a little with it), so I'm sorry if some of my APIs are not perfect. I also cut things in jamba-style java8 just for the convenience of writing and reading. If you are not using something like retrolambda, it is still pretty easy to convert it.

 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 take this outside the scope of the action as needed, and simply pass an instance of the Activity / Fragment to bind the lifecycle, as well as an Observable<Realm> , which in this case will be AsyncSubject . If there is still a race condition due to the subscription, you can experiment with adding .unsubscribeOn(AndroidSchedulers.mainThread()) or even .unsubscribeOn(Schedulers.immediate()) (in fact, I’m not sure that it would be better in this scripts) before bindToRealm to ensure that you unsubscribe if you want it to be in onPause before the Realm instance is closed.

+1
source

Source: https://habr.com/ru/post/988475/


All Articles