2016-02-26 17 views
12

W moich projektach Android używam realm jako mojego silnika do przechowywania danych. Kocham to!
Używam również RxJava, ponieważ sprawia, że ​​"gwintowanie" jest o wiele łatwiejsze i bardzo podoba mi się cały "reaktywny sposób myślenia". Kocham to!
Realm, RxJava, asObservable() i doOnUnsubscribe()

Używam wzorca MVP + trochę pomysłów "Czysta architektura" do budowania moich aplikacji.

Moje Interactors to jedyne osoby, które wiedzą o Realm. I narazić danych za pomocą Obserwowalne, tak:

@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>()); 
} 

Problem jest mój doOnUnsubscribe efektem ubocznym jest wywoływana przed Realm może zrobić jego rzecz, obsługi odsłonięta obserwowalne:

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) 

Stworzyłem projekt sandbox dla tego przypadku użycia.

Bardzo lubię używać Realm + RxJava, ale nie mogę znaleźć czystego rozwiązania dla close instancji Realm, kiedy I unsubscribe (zwykle rezygnuję z subskrypcji, gdy działanie zostanie zniszczone). Jakieś pomysły?

Edycja 1: https://github.com/realm/realm-java/issues/2357
Edycja 2: dzięki bardzo aktywnym zespołem realm, nie jest już pull request aby rozwiązać ten problem.

Odpowiedz

1

21 godzin później i to, co wymyśliłem:

@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); 
     } 
    }); 
} 

Próbowałem coś takiego przed zaksięgowaniem pytanie na stackoverflow, ale mój błąd był przy flatMap(), zamiast concatMap().

przeciwieństwie flatMap(), concatMap() zachowa kolejność emisji, które w moim przypadku, to znaczy, że mój Action0 -> realm.close() będzie ostatnią czynność miano po wypisywania ze strumienia, po Realm na Action0 -> results.removeChangeListener(listener) który był przyczyną problemu.

Pełny przykład można znaleźć pod adresem github.

Edytuj: dzięki bardzo aktywnemu zespołowi ds. Rzeczywistości istnieje już pull request, aby rozwiązać ten problem.

+0

Rozważ pakowanie Realm przez Observable # przy użyciu, to uprości twoją metodę getManagedRealm(): http://pastebin.com/CrzryvCq –

+0

Próbuję twoje rozwiązanie i stanąłem przed problemem: zbyt wiele otwartych w pliku –

0

Skoro powiedział, że tylko interaktora „wie” o ramach Realm Powiedziałbym nawet nie wrócić do zarządzanego obiektu domeny, zamiast wrócić niezarządzanego kopię wyników przy użyciu copyFromRealm. W ten sposób nie musisz przejmować się faktem, że instancja Realm jest otwarta lub zamknięta w Presenter.

Jednocześnie chciałbym niech Presenter wybrał pogoda połączenie powinno być wykonane asynchroniczny lub nie od RxJava robi całkiem fajne i proste i nie będzie mieć problemy z wywołaniem interaktora metodę obciążenia w innym wątku (którego można uniknąć za pomocą Loopers, ale po co komplikować sytuację, jeśli możesz zrobić to prościej: P).

Więc pójdę do:

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); 
} 

Jestem ciekaw, aby zobaczyć więcej opcji :).

+1

Wracając do obserwowalnego , mój interaktor w pełni obejmuje "reaktywny sposób myślenia". Kiedy wykonasz zapytanie, Interactor zwróci strumień, który popchnie dane, gdy będą gotowe i dostępne. Gdy nowe dane są dostępne, zwrócony strumień jest gotowy do przekazania tych danych. W moim przypadku dostanę zaktualizowane miasto za każdym razem, gdy ktoś inny pchnie zmiany w królestwie. "Automatyczne odświeżanie" jest podstawową cechą Królestwa i jest bardzo przyjemne. –

+0

Ponadto, copyFromRealm() jest w porządku dla mojej wersji demonstracyjnej, ponieważ moje obiekty są małe bez żadnych relacji. Ale w prawdziwym życiu mam złożony wykres obiektów i naprawdę nie chcę załadować ich wszystkich do pamięci. "Lazy + no-copy data" to kolejna główna cecha Królestwa, którą powinieneś w pełni wykorzystać, kiedy tylko możesz. –

+0

Realm ma ograniczenie "threading" i nie można używać RxJava do obsługi wielowątkowych zapytań. Istnieje dobry przykładowy projekt, który możesz eksplorować, aby uzyskać więcej gotcha: https://github.com/realm/realm-java/blob/master/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/gotchas /GotchasActivity.java –

0

Według mnie jedną z najważniejszych rzeczy, którymi należy się zająć w dobrej architekturze, jest modułowość. Wszystkie główne moduły (lub biblioteki) powinny być odizolowane od reszty kodu. Ponieważ Realm, RealmObject lub RealmResult nie mogą być przekazywane przez wątki, jeszcze ważniejsze jest, aby Realm & powiązane z Realem operacje były odizolowane od reszty kodu.

Trzymając się tej filozofii, wymyśliłem następujące podejście.

Dla każdej klasy jsonModel tworzymy klasę realmModel i DAO (Data Access Object). Pomysł tutaj jest taki, że poza klasą DAO żadna z klas nie musi znać ani nie mieć dostępu do jednostek realmModel lub Realm. Klasa DAO pobiera jsonModel, zamienia ją na realmModel, wykonuje operacje odczytu/zapisu/edycji/usuwania & dla operacji odczytu DAO konwertuje wynikowy model realmModel na jsonModel i zwraca je.

W ten sposób można łatwo zarządzać dziedziną, unikać wszystkich problemów związanych z wątkami, łatwo testować i debugować.

Oto artykuł o Realm najlepszych praktyk z dobrym architechture https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f

również projekt próba wykazania Integracja Realm na Androida z MVP (model widok Presenter), RxJava, modernizowanych Dagger adnotacje & Testing. https://github.com/viraj49/Realm_android-injection-rx-test