2016-02-26 4 views
12

В моих проектах для Android я использую realm в качестве механизма хранения данных. Я люблю это!
Я также использую RxJava, потому что это упрощает «нарезку», и мне очень нравится весь «реактивный образ мышления». Я люблю это!
Realm, RxJava, asObservable() и doOnUnsubscribe()

Я использую шаблон 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) 

Я создал a sandbox проект для этого варианта использования.

Мне очень нравится использовать Realm + RxJava, но я не могу найти чистого решения для close экземпляра Realm, когда я unsubscribe (Обычно я отключаюсь, когда действие уничтожается). Есть идеи?

Edit 1: https://github.com/realm/realm-java/issues/2357
Edit 2: благодаря очень активной команде царств, есть уже pull request, чтобы решить эту проблему.

ответ

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.

Редактировать: благодаря очень активной команде в сфере, для решения этой проблемы уже существует pull request.

+0

Рассмотрите обертывание Realm через Observable # using, это упростит ваш метод getManagedRealm(): http://pastebin.com/CrzryvCq –

+0

Я пробую ваше решение, и у меня возникла проблема: слишком много открытых в файле realm –

0

Поскольку вы сказали, что только 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); 
} 

Мне любопытно увидеть больше вариантов :).

+1

Возвращение наблюдаемого , мой Интерактор полностью охватывает «реактивное мышление». Когда вы выполняете запрос, Interactor вернет поток, который будет выталкивать данные, когда он будет готов и доступен. Когда новые данные доступны, возвращенный поток готов подтолкнуть вас к этим данным. В моем случае, я получаю обновленный город каждый раз, когда кто-то другой вносит изменения в царство. «Автоматическое обновление» - это основная особенность Realm, и очень приятная. –

+0

Кроме того, copyFromRealm() подходит для моей демонстрации, потому что мои объекты маленькие, без связей. Но в реальной жизни у меня сложный граф объектов, и я действительно не хочу загружать их все в память. «Lazy + no-copy data» - это еще одна основная особенность Realm, что вы должны в полной мере использовать все возможности. –

+0

У Realm есть ограничение «threading», и вы не можете использовать RxJava для обработки многопоточных запросов. Существует хороший пример проекта, который вы можете исследовать для получения дополнительных файлов: https://github.com/realm/realm-java/blob/master/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas /GotchasActivity.java –

0

Как мне, одна из основных вещей, которые нужно учесть в хорошей архитектуре, - это модульность. Все основные модули (или библиотеки) должны быть изолированы от остальной части кода. Поскольку Realm, RealmObject или RealmResult не могут передаваться по потокам, еще важнее сделать Realm & Операции, связанные с царством, изолированы от остальной части кода.

Помня об этой философии, я придумал следующий подход.

Для каждого класса jsonModel мы создаем класс realmModel и класс DAO (Data Access Object). Идея здесь заключается в том, что кроме класса DAO ни один из классов не должен знать или получать доступ к объектам realmModel или Realm. Класс DAO принимает jsonModel, преобразует его в realmModel, выполняет операции чтения/записи/редактирования/удаления & для операций чтения. DAO преобразует приведенные realmModel в jsonModel и возвращает их.

Таким образом, легко поддерживать Realm, избегать всех проблем, связанных с потоком, легко тестировать и отлаживать.

Вот статья о Realm передовой практике с хорошей Architechture https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f

Также образец проекта, демонстрирующего Интеграция Realm на Android с MVP (Model View Presenter), RxJava, дооборудования, Dagger, аннотаций & тестирования. https://github.com/viraj49/Realm_android-injection-rx-test

Смежные вопросы