2016-06-27 4 views
4

Я реализую сетевой API с комбинацией RxJava и Retrofit, и я использую Realm в качестве моей базы данных. Я получил это довольно много работы, но мне интересно, если это правильный подход и поток событий. Итак, вот RetrofitApiManager.Правильный поток в RxJava с дооснащением и недвижимостью

public class RetrofitApiManager { 

    private static final String BASE_URL = "***"; 

    private final ShopApi shopApi; 

    public RetrofitApiManager(OkHttpClient okHttpClient) { 

     // GSON INITIALIZATION 

     Retrofit retrofit = new Retrofit.Builder() 
       .client(okHttpClient) 
       .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
       .addConverterFactory(GsonConverterFactory.create(gson)) 
       .baseUrl(BASE_URL) 
       .build(); 

     shopApi = retrofit.create(ShopApi.class); 
    } 

    public Observable<RealmResults<Shop>> getShops() { 
     return shopApi.getShops() 
       .subscribeOn(Schedulers.io()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .doOnNext(response -> { 
        Realm realm = Realm.getDefaultInstance(); 
        realm.executeTransaction(realm1 -> 
          realm1.copyToRealmOrUpdate(response.shops)); 
        realm.close(); 
       }) 
       .flatMap(response -> { 
        Realm realm = Realm.getDefaultInstance(); 
        Observable<RealmResults<Shop>> results = realm.where(Shop.class) 
          .findAllAsync() 
          .asObservable() 
          .filter(RealmResults::isLoaded); 
        realm.close(); 
        return results; 
       }); 
    } 
} 

А вот вызов, чтобы получить RealmResults<Shop> внутри Fragment.

realm.where(Shop.class) 
     .findAllAsync() 
     .asObservable() 
     .filter(RealmResults::isLoaded) 
     .first() 
     .flatMap(shops -> 
       shops.isEmpty() ? retrofitApiManager.getShops() : Observable.just(shops)) 
     .subscribe(
       shops -> initRecyclerView(), 
       throwable -> processError(throwable)); 

Вот мои вопросы:

  1. ли это правильный подход к цепи событий, как в примере выше, или я должен управлять им по-другому?

  2. Можно ли использовать Realm экземпляр в методе getShops() и закрыть его там, или было бы лучше передать его в качестве аргумента, а затем каким-то образом управлять им? Хотя эта идея кажется немного проблематичной для потоков и вызывает Realm.close() всегда в нужное время.

ответ

10

1) Я постараюсь сделать как можно больше на фоне потока, прямо сейчас вы делаете большую работу над потоком пользовательского интерфейса.

2)

public Observable<RealmResults<Shop>> getShops() { 
     return shopApi.getShops() 
       .subscribeOn(Schedulers.io()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .doOnNext(response -> { 
        try(Realm realm = Realm.getDefaultInstance()) { 
         realm.executeTransaction(realm1 -> 
          realm1.insertOrUpdate(response.shops)); 
        } // auto-close 
       }) 
       .flatMap(response -> { 
        try(Realm realm = Realm.getDefaultInstance()) { 
         Observable<RealmResults<Shop>> results = realm.where(Shop.class) 
          .findAllAsync() 
          .asObservable() 
          .filter(RealmResults::isLoaded); 
        } // auto-close 
        return results; 
       }); 
    } 

Все данные Realm ленивым загружены, так что он доступен только в то время как экземпляр Realm открыта, так что закрытие его после извлечения она имеет высокий шанс не работает. В вашем случае, хотя вы плоское сопоставление в основном потоке, так что, скорее всего, там есть открытый экземпляр.

Если вы хотите, вы можете использовать copyFromRealm(), чтобы получить неуправляемые данные, которые можно перемещать по потокам и больше не подключаться к Realm, но они также потеряют свои функции обновления и увеличат объем памяти.

Это, вероятно, сделать это вместо того, чтобы:

public Observable<RealmResults<Shop>> getShops() { 
     return shopApi.getShops() 
       .subscribeOn(Schedulers.io()) 
       .doOnNext(response -> { 
        try(Realm realm = Realm.getDefaultInstance()) { 
         realm.executeTransaction(realm1 -> 
          realm1.copyToRealmOrUpdate(response.shops)); 
        } // auto-close 
       }) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .flatMap(response -> { 
        Observable<RealmResults<Shop>> results = realm.where(Shop.class) 
          .findAllAsync() 
          .asObservable() 
          .filter(RealmResults::isLoaded); 
        return results; 
       }); 

В качестве альтернативы вы можете обращаться с сетевой запрос, как побочный эффект, и только зависит от Realm уведомления вас, когда есть изменения (лучше подход ИМО, как вы отделить сеть от доступ к БД, которая является, например, то, что Repository картина о)

public Observable<RealmResults<Shop>> getShops() { 
    // Realm will automatically notify this observable whenever data is saved from the network 
    return realm.where(Shop.class).findAllAsync().asObservable() 
      .filter(RealmResults::isLoaded) 
      .doOnNext(results -> { 
       if (results.size() == 0) { 
        loadShopsFromNetwork(); 
       } 
      }); 
} 

private void loadShopsFromNetwork() { 
    shopApi.getShops() 
      .subscribeOn(Schedulers.io()) 
      .subscribe(response -> { 
       try(Realm realm = Realm.getDefaultInstance()) { 
        realm.executeTransaction(r -> r.insertOrUpdate(response.shops)); 
       } // auto-close 
      }); 
} 
+0

Я не думаю, что возвращение наблюдаемого в первом фрагменте кода из 2) будет работать, потому что Observable принадлежит экземпляру Realm, который закрывается сразу после получения асинхронного запроса. Но последнее довольно хорошо – EpicPandaForce

+0

@ChristianMelchior @EpicPandaForce Какие могут быть помехи от создания экземпляра нового объекта Realm в 'doOnNext', как это? Не могло ли это стать плохо, если 'doOnNext()' вызывались много раз подряд? Кроме того, какие эффекты оказывает это на объекты, которые вставлены/обновлены, поскольку они вставляются через недавно созданное Realm вместо Realm, которое будет создано в основном потоке? (Искренне прося о моих знаниях). – w3bshark

+0

Нашел ответ на мои вопросы здесь: https://realm.io/news/viraj-tank-safe-vs-deep-integration – w3bshark

1

Что Кристиан Мельхиор упомянул в своем ответе, имеет смысл, и должно решить проблему вы имеете в руке, но вниз линии такой подход может ввести другие вопросы).

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

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

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

Вот статья о 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

+1

Разве этот подход не пропускает значительную часть автообновлений 'RealmObjects'? Предположим, что у меня есть 'Shop' под названием« shopA », и я меняю его на« shopB »где-то в коде. С 'RealmResults ' как список для моего adpater он автоматически обновляет отображение с помощью правильного 'RealmChangeListner' внутри адаптера. Если бы я разделил его с DAO и использовал jsonModel, это было бы не так просто или я что-то упустил? Также о потоковом - я не знаю, это может быть проблемой, но почему вы пытаетесь передать «RealmObjects» между потоками в первую очередь? –

+0

Это правда, что вы пропустите способ автоматического обновления Realm, но если вы используете RxJava, гораздо проще и безопаснее использовать RxJava для обновления представления на основе обновленного jsonModel. Крупнейшая точка продаж в сфере недвижимости - скорость, простота и простая интеграция, но с использованием автоматического обновления Realm мы пропустим часть простоты и должны изменить уже проверенный код, чтобы доставить Realm на борт. –

+0

Дополнение: Я не передаю RealmObjects между потоками, вот и весь смысл этого подхода, и мне также не нужно делать никаких операций с Realm на MainThread. –

1

В моем случае, я, кажется, определили запрос для RealmRecyclerViewAdapter как это:

recyclerView.setAdapter(new CatAdapter(getContext(), 
      realm.where(Cat.class).findAllSortedAsync(CatFields.RANK, Sort.ASCENDING))); 

И в противном случае определено условие для Модернизированный с RxJava скачать больше вещей, когда условие:

Subscription downloadCats = Observable.create(new RecyclerViewScrollBottomOnSubscribe(recyclerView)) 
      .filter(isScrollEvent -> isScrollEvent || realm.where(Cat.class).count() <= 0) 
      .switchMap(isScrollEvent -> catService.getCats().subscribeOn(Schedulers.io())) // RETROFIT 
      .retry() 
      .subscribe(catsBO -> { 
       try(Realm outRealm = Realm.getDefaultInstance()) { 
        outRealm.executeTransaction((realm) -> { 
         Cat defaultCat = new Cat(); 
         long rank; 
         if(realm.where(Cat.class).count() > 0) { 
          rank = realm.where(Cat.class).max(Cat.Fields.RANK.getField()).longValue(); 
         } else { 
          rank = 0; 
         } 
         for(CatBO catBO : catsBO.getCats()) { 
          defaultCat.setId(catBO.getId()); 
          defaultCat.setRank(++rank); 
          defaultCat.setSourceUrl(catBO.getSourceUrl()); 
          defaultCat.setUrl(catBO.getUrl()); 
          realm.insertOrUpdate(defaultCat); 
         } 
        }); 
       } 
      }, throwable -> { 
       Log.e(TAG, "An error occurred", throwable); 
      }); 

И это, например, поиск на основе ввода правку текста:

Subscription filterDogs = RxTextView.textChanges(editText) 
        .switchMap((charSequence) -> 
          realm.where(Dog.class) 
           .contains(DogFields.NAME, charSequence.toString()) 
           .findAllAsyncSorted(DogFields.NAME, Sort.ASCENDING) 
           .asObservable()) 
        .filter(RealmResults::isLoaded) 
        .subscribe(dogs -> realmRecyclerAdapter.updateData(dogs)); 
Смежные вопросы