8

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

我需要从远程服务中获取数据并将其保存到房间数据库中。我希望我的视图只观察一个实时数据列表。我的 AppRepository 处理 RemoteRepository 和 LocalRepository。Remote Repository 有一个 fetchMovies() 方法,它从 web 服务接收电影列表。我想将此列表保存在房间数据库中,目前我的 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) 方法是否应该为此返回实时数据,但在这种情况下,如何在我的视图模型中处理它,该视图模型目前具有房间返回的电影列表的实时数据。

@Query("SELECT * from movie ORDER BY id ASC")
LiveData<List<Movie>> getAllWords();

我是 MVVM 的新手,请指导我了解此架构的理想方法。

4

2 回答 2

7

我采用了 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;
    }
}

这是使用该类的回购:

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

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

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

在此处输入图像描述

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

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

于 2018-08-15T19:56:31.017 回答
0

viewmodel 和存储库之间的两种通信方式是

  1. 使用回调接口
  2. 使用转换。

Transformations 具有类似于 RxJava 的 map 和 switchMap 运算符,可以将 livedata 转换为不同的

您还可以使用 Mediator livedata 创建您自己的自定义运算符。MediatorLiveData 用于观察多个实时数据源并在其中执行更改。

有关 MVVVM 和实时数据的更多信息

于 2018-12-01T06:32:11.377 回答