Realm, RxJava, asObservable() и doOnUnsubscribe()
В моих проектах Android я использую realm в качестве механизма хранения данных. Я люблю это!
Я также использую RxJava, потому что он делает "threading" намного проще, и мне очень нравится весь "реактивный образ мышления". Я люблю это!
Я использую шаблон MVP + некоторые идеи "чистой архитектуры" для создания моих приложений.
Мои Interactors
являются единственными, кто знает о Realm
. Я выставляю данные с помощью Observable, например:
@Override
public Observable<City> getHomeTown() {
final Realm realm = Realm.getDefaultInstance();
return realm.where(City.class).equalTo("name", "Cluj-Napoca").findAllAsync().asObservable()
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
realm.close();
}
})
.compose(new NullIfNoRealmObject<City>());
}
Проблема заключается в том, что мой побочный эффект doOnUnsubscribe
вызывается до того, как Realm
может выполнять свою работу, обрабатывая видимые видимые объекты:
Caused by: java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:344)
at io.realm.RealmResults.removeChangeListener(RealmResults.java:818)
at io.realm.rx.RealmObservableFactory$3$2.call(RealmObservableFactory.java:137)
at rx.subscriptions.BooleanSubscription.unsubscribe(BooleanSubscription.java:71)
at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
at rx.Subscriber.unsubscribe(Subscriber.java:98)
at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
at rx.Subscriber.unsubscribe(Subscriber.java:98)
at rx.subscriptions.CompositeSubscription.unsubscribeFromAll(CompositeSubscription.java:150)
at rx.subscriptions.CompositeSubscription.unsubscribe(CompositeSubscription.java:139)
at ro.tudorluca.realm.sandbox.city.CityPresenter.onDestroy(CityPresenter.java:62)
at ro.tudorluca.realm.sandbox.city.CityActivity.onDestroy(CityActivity.java:35)
Я создал проект sandbox для этого варианта использования.
Мне действительно нравится использовать Realm + RxJava, но я не могу найти чистого решения для close
экземпляра Realm, когда я unsubscribe
(обычно я отказываюсь от подписки, когда действие уничтожается). Любые идеи?
Изменить 1: https://github.com/realm/realm-java/issues/2357
Изменить 2: благодаря очень активной команде realm, для исправления этой проблемы уже существует запрос на перенос.
Ответы
Ответ 1
21 час спустя, и вот что я придумал:
@Override
public Observable<City> getHomeTown() {
return getManagedRealm()
.concatMap(new Func1<Realm, Observable<City>>() {
@Override
public Observable<City> call(Realm realm) {
return realm.where(City.class).equalTo("name", "Cluj-Napoca").findAllAsync().asObservable()
.compose(new NullIfNoRealmObject<City>());
}
});
}
private static Observable<Realm> getManagedRealm() {
return Observable.create(new Observable.OnSubscribe<Realm>() {
@Override
public void call(final Subscriber<? super Realm> subscriber) {
final Realm realm = Realm.getDefaultInstance();
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
realm.close();
}
}));
subscriber.onNext(realm);
}
});
}
Я попробовал что-то вроде этого, прежде чем публиковать вопрос о stackoverflow, но моя ошибка заключалась в использовании flatMap()
вместо concatMap()
.
В отличие от flatMap()
, concatMap()
будет поддерживать порядок выбросов, что в моем случае означает, что мой Action0 -> realm.close()
будет последним действием, вызываемым после отмены подписки из потока, после Realm Action0 -> results.removeChangeListener(listener)
, который был вызывая проблему.
Полный пример можно найти на github.
Изменить: благодаря очень активной команде realm, для исправления этой проблемы уже существует pull request.
Ответ 2
Поскольку вы сказали, что только Interactor "знает" об инфраструктуре Realm, я бы сказал, что даже не вернул управляемый объект Realm, вместо этого вернет неуправляемую копию результатов, используя copyFromRealm
. Таким образом, вам не нужно заботиться о том, чтобы экземпляр Realm был открыт или закрыт в Presenter.
В то же время я бы позволил Presenter выбрать, что вызов должен выполняться асинхронно или нет, поскольку RxJava
делает это довольно круто и легко, и у вас не будет проблем, вызывающих метод загрузки Interactor в другом потоке (который может избегайте использования Loopers, но почему это затрудняет ситуацию, если вы можете сделать это проще: P).
Итак, я бы пошел за:
Override
public Observable<City> getHomeTown() {
final Realm realm = Realm.getDefaultInstance();
City city = realm.where(City.class).equalTo("name", "Cluj-Napoca").findFirst();
// make sure we don't send back Realm stuff, this is a deep copy that will copy all referenced objects (as the method doc says)
City cityUnmanaged = realm.copyFromRealm(city);
// safe to close the realm instance now
realm.close();
return Observable.just(cityUnmanaged);
}
Мне интересно узнать больше вариантов:).
Ответ 3
Как мне кажется, одна из главных вещей, которые нужно учесть в хорошей архитектуре, - это модульность. Все основные модули (или библиотеки) должны быть изолированы от остальной части кода. Поскольку Realm, RealmObject или RealmResult не могут быть переданы по потокам, еще важнее сделать операции, связанные с Realm и Realm, изолированными от остальной части кода.
Помня эту философию, я придумал следующий подход.
Для каждого класса jsonModel мы создаем класс realmModel и класс DAO (Data Access Object). Идея здесь заключается в том, что кроме класса DAO ни один из классов не должен знать или получать доступ к объектам realmModel или Realm. Класс DAO принимает jsonModel, преобразует его в realmModel, выполняет операции чтения/записи/редактирования/удаления, а для операций чтения DAO преобразует приведенные realmModel в jsonModel и возвращает их.
Таким образом, легко поддерживать Realm, избегать всех проблем, связанных с потоком, легко тестировать и отлаживать.
Вот статья о лучших практиках Realm с хорошей архитектурой https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
Также показан пример проекта, демонстрирующий интеграцию Realm на Android с MVP (Presentation Viewer), RxJava, Retrofit, Dagger, Annotations and Testing. https://github.com/viraj49/Realm_android-injection-rx-test