android-room - Android 架构组件(MVVM) - 使用存储库模式处理远程和本地数据的理想方式

标签 android-room android-architecture-components android-livedata android-mvvm

我浏览了许多可用于新架构组件的示例代码,但我在设置项目时仍然面临一些问题。

我需要从我的远程服务中获取数据并将其保存到房间数据库中。我希望我的 View 只观察一个实时数据列表。我的 AppRepository 处理 RemoteRepository 和 LocalRepository。远程存储库有一个 fetchMovies() 方法,它从网络服务接收电影列表。我想将此列表保存在房间数据库中,目前我的 RemoteRepository 类是这样做的。

public void fetchMovieFromRemote(int page){
    movieService.fetchPopularMovies(ApiConstants.BASE_URL, page).enqueue(new Callback<List<Movie>>() {
        @Override
        public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
            int statusCode = response.code();
            if (statusCode == 200){
                mLocalRepository.insert(response.body());
            }
        }
        @Override
        public void onFailure(Call<List<Movie>> call, Throwable t) {
        }
    });
}

根据我的理解,理想情况下,远程和本地存储库应该是独立的,这项工作应该由 AppRepository 类完成。
一种方法是使用回调,但我想使用实时数据来做到这一点。 fetchMovieFromRemote(int page) 方法是否应该为此返回实时数据,但在这种情况下如何在我的 View 模型中处理它,该 View 模型目前具有由房间返回的电影列表的实时数据。
@Query("SELECT * from movie ORDER BY id ASC")
LiveData<List<Movie>> getAllWords();

我是 MVVM 的新手,请指导我了解这种架构的理想方法是什么。

最佳答案

我采用了 Google 在他们的示例中用于存储库的模式,该模式为您提供单一的真实来源(您的 Room 数据库)。

在这里讨论:https://developer.android.com/jetpack/docs/guide

要注意的关键部分是 NetworkBoundResource 类(Google 示例:https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.kt)。这个来自 Google 的例子在 Kotlin 中,我确实找到了一个 Java 例子。

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package iammert.com.androidarchitecture.data;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import android.os.AsyncTask;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public abstract class NetworkBoundResource<ResultType, RequestType> {
    private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();

    @MainThread
    NetworkBoundResource() {
        result.setValue(Resource.loading(null));
        LiveData<ResultType> dbSource = loadFromDb();
        result.addSource(dbSource, data -> {
            result.removeSource(dbSource);
            if (shouldFetch(data)) {
                fetchFromNetwork(dbSource);
            } else {
                result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
            }
        });
    }

    private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
        result.addSource(dbSource, newData -> result.setValue(Resource.loading(newData)));
        createCall().enqueue(new Callback<RequestType>() {
            @Override
            public void onResponse(Call<RequestType> call, Response<RequestType> response) {
                result.removeSource(dbSource);
                saveResultAndReInit(response.body());
            }

            @Override
            public void onFailure(Call<RequestType> call, Throwable t) {
                onFetchFailed();
                result.removeSource(dbSource);
                result.addSource(dbSource, newData -> result.setValue(Resource.error(t.getMessage(), newData)));
            }
        });
    }

    @MainThread
    private void saveResultAndReInit(RequestType response) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                saveCallResult(response);
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                result.addSource(loadFromDb(), newData -> result.setValue(Resource.success(newData)));
            }
        }.execute();
    }

    @WorkerThread
    protected abstract void saveCallResult(@NonNull RequestType item);

    @MainThread
    protected boolean shouldFetch(@Nullable ResultType data) {
        return true;
    }

    @NonNull
    @MainThread
    protected abstract LiveData<ResultType> loadFromDb();

    @NonNull
    @MainThread
    protected abstract Call<RequestType> createCall();

    @MainThread
    protected void onFetchFailed() {
    }

    public final LiveData<Resource<ResultType>> getAsLiveData() {
        return result;
    }
}

这是使用该类的 repo :
package iammert.com.androidarchitecture.data;

import android.arch.lifecycle.LiveData;
import android.support.annotation.NonNull;

import java.util.List;

import javax.inject.Inject;

import iammert.com.androidarchitecture.data.local.dao.MovieDao;
import iammert.com.androidarchitecture.data.local.entity.MovieEntity;
import iammert.com.androidarchitecture.data.remote.MovieDBService;
import iammert.com.androidarchitecture.data.remote.model.MoviesResponse;
import retrofit2.Call;

/**
 * Created by mertsimsek on 19/05/2017.
 */

public class MovieRepository {

    private final MovieDao movieDao;
    private final MovieDBService movieDBService;

    @Inject
    public MovieRepository(MovieDao movieDao, MovieDBService movieDBService) {
        this.movieDao = movieDao;
        this.movieDBService = movieDBService;
    }

    public LiveData<Resource<List<MovieEntity>>> loadPopularMovies() {
        return new NetworkBoundResource<List<MovieEntity>, MoviesResponse>() {

            @Override
            protected void saveCallResult(@NonNull MoviesResponse item) {
                movieDao.saveMovies(item.getResults());
            }

            @NonNull
            @Override
            protected LiveData<List<MovieEntity>> loadFromDb() {
                return movieDao.loadMovies();
            }

            @NonNull
            @Override
            protected Call<MoviesResponse> createCall() {
                return movieDBService.loadMovies();
            }
        }.getAsLiveData();
    }

    public LiveData<MovieEntity> getMovie(int id){
        return movieDao.getMovie(id);
    }
}

我将尝试简要解释一下,您的 Repo 中有一个方法,例如 loadMovies(),它从您的存储库中返回电影的 LiveData 列表。使用 NetworkBoundResource,首先检查 Room 数据库,然后查询 API,然后将结果加载到数据库中。数据库更新后,您正在观察的 LiveData 将使用新结果进行更新。

此图显示了这背后的逻辑流程:

enter image description here

正如您在上面看到的,您正在观察磁盘的变化,并在它发生变化时收到更新。

我建议通读我之前链接的那个 Jetpack 指南,因为他们会更详细地解释它。我相信这就是你要找的。

关于android-room - Android 架构组件(MVVM) - 使用存储库模式处理远程和本地数据的理想方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51865202/

相关文章:

android - 使用 WorkManager 更新 Room 中的数据库条目

android - 是否可以通过 Room 创建不区分大小写的索引?

安卓工作室 : Room: error: Cannot find getter for field

安卓架构组件 : Multiple instances of the same view model

android - 如果我点击导航 Controller 中的菜单,如何在两个 fragment 目的地之间传递数据?

android - Livedata 观察者的多个实例

android - 重命名列失败的房间自动迁移(NOT NULL 约束失败,错误生成的迁移类)

android - MVVM Dagger2 组件中存在具有匹配键的绑定(bind)

android - 在 Kotlin 中打开新 fragment 时如何对当前 fragment 应用更改?

android - 具有 View 模型的 DialogFragment 无法使用数据绑定(bind)