简体   繁体   English

使用Retrofit和Realm在RxJava中纠正流程

[英]Correct flow in RxJava with Retrofit and Realm

I'm implementing network API with the combination of RxJava and Retrofit, and I use Realm as my database. 我正在使用RxJava和Retrofit的组合实现网络API,我使用Realm作为我的数据库。 I got it pretty much working but I'm wondering if it is the correct approach and flow of events. 我得到了相当多的工作,但我想知道它是否是正确的方法和事件的流程。 So, here is the RetrofitApiManager . 所以,这是RetrofitApiManager

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

And here is the call to get RealmResults<Shop> inside a Fragment . 这是调用Fragment RealmResults<Shop>的调用。

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

Here are my questions: 这是我的问题:

  1. Is it a correct approach to chain events like in the example above or should I manage them in a different way? 它是链接事件的正确方法,如上例所示,还是应该以不同的方式管理它们?

  2. Is it OK to use Realm instance in getShops() method and close i there or would it be better to pass it as an argument and then manage it somehow? 可以在getShops()方法中使用Realm实例并在getShops()关闭我或者将它作为参数传递然后以某种方式管理它会更好吗? Although, this idea seems to be a bit problematic with threads and calling Realm.close() always at the right time. 虽然,这个想法似乎对线程有点问题,并且总是在正确的时间调用Realm.close()

1) I would try to do as much as possible on the background thread, right now you are doing a lot of the work on the UI thread. 1)我会尝试在后台线程上尽可能多地做,现在你在UI线程上做了很多工作。

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

All Realm data is lazy-loaded, so it is only available while the Realm instance is open, so closing it after retrieving it has a high chance of not working. 所有Realm数据都是延迟加载的,所以它只在Realm实例打开时才可用,因此在检索它之后关闭它很有可能无法正常工作。 In your case though you are flat-mapping on the main thread, so most likely there is already an open instance there. 在你的情况下,虽然你是主线程上的平面映射,所以很可能已经有一个打开的实例。

If you want you can use copyFromRealm() to get unmanaged data out that can be moved across threads and are not connected to Realm anymore, but they will also loose their live update features and take up more memory. 如果您愿意,可以使用copyFromRealm()来获取可以跨线程移动的非托管数据,并且不再连接到Realm,但是它们也将失去其实时更新功能并占用更多内存。

It would probably do this instead: 它可能会这样做:

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

Alternatively you can treat the network request as a side-effect and just depend on Realm notifying you when there is changes (better approach IMO as you separate network from DB access which is eg what the Repository pattern is about) 或者,您可以将网络请求视为副作用,并且只是依赖于Realm在有更改时通知您(更好地接近IMO,因为您将网络与数据库访问分开,例如存储库模式的内容)

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

What Christian Melchior mentioned in his answer, makes perfect sense, and should solve the problem you are having at your hand, but down the line this approach may introduce other issue(s). Christian Melchior在他的回答中提到的是完全合理的,并且应该解决你手头的问题,但是这种方法可能会引入其他问题。

In a good architecture, all the major modules(or libraries) should be isolated from rest of the code. 在良好的体系结构中,所有主要模块(或库)都应与其余代码隔离开来。 Since Realm, RealmObject or RealmResult can not be passed across threads it is even more important to make Realm & Realm related operations isolated from rest of the code. 由于Realm,RealmObject或RealmResult不能跨线程传递,因此将Realm&Realm相关操作与其余代码隔离更为重要。

For each of your jsonModel class, you should have a realmModel class and a DAO (Data Access Object). 对于每个jsonModel类,您应该有一个realmModel类和一个DAO(数据访问对象)。 Idea here is that other than DAO class none of the class must know or access realmModel or Realm. 这里的想法是除了DAO类之外,没有一个类必须知道或访问realmModel或Realm。 DAO class takes jsonModel, converts to realmModel, performs read/write/edit/remove operations, for read operations DAO converts realmModel to jsonModel and returns with it. DAO类接受jsonModel,转换为realmModel,执行读/写/编辑/删除操作,对于读取操作,DAO将realmModel转换为jsonModel并随之返回。

This way it is easy to maintain Realm, avoid all Thread related issues, easy to test and debug. 这样就很容易维护Realm,避免所有Thread相关的问题,易于测试和调试。

Here is an article about Realm best practices with a good architechture https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f 这是一篇关于Realm最佳实践的文章,它有一个很好的架构https://medium.com/@Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f

Also a sample project demonstrating Integration of Realm on Android with MVP(Model View Presenter), RxJava, Retrofit, Dagger, Annotations & Testing. 此外,还展示了一个示例项目,演示了Android上的Realm与MVP(模型视图演示器),RxJava,Retrofit,Dagger,Annotations和Testing的集成。 https://github.com/viraj49/Realm_android-injection-rx-test https://github.com/viraj49/Realm_android-injection-rx-test

In my case, I seem to have defined a query for the RealmRecyclerViewAdapter like this: 就我而言,我似乎已经为RealmRecyclerViewAdapter定义了一个查询:

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

And otherwise defined a condition for Retrofit with RxJava to download more stuff when the condition is met: 并且在满足条件时使用RxJava定义Retrofit条件以下载更多内容:

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

And this is for example a search based on an edit text's input: 这是基于编辑文本输入的搜索:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM