Android Architecture Components(MVVM)-使用存储库模式处理远程和本地数据的理想方法

时间:2018-08-15 19:31:13

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

我浏览了许多可用于新体系结构组件的示例代码,但是在设置项目时仍然遇到一些问题。

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

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

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

3 个答案:

答案 0 :(得分:1)

让存储库完成获取数据的任务。最好让它们独立。因此,让调用活动获取数据(使用远程存储库获取电影)。然后从“活动”中使用LocalRepository更新数据库。我已经使用RxJava进行了Api呼叫。

public interface onSuccessListener {
    void onSuccess(String response);
}

在gradle中为RxJavaRxAndroid添加以下依赖项的最新版本。 room-rxjava可能会有帮助

// RxJava
    implementation 'io.reactivex.rxjava2:rxjava:2.1.1'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    implementation 'android.arch.persistence.room:rxjava2:1.0.0'

您的活动

class YourActivity {
   int page;

   RemoteRepository remoteRepo;
   LocalViewModel localViewModel;

  // Initialise the above variables in the onCreate Method
  private void onCreate() {

     createObserver();
     loadMovies();
  }
   .
   .
   .
    void loadMovies() {
       remoteRepo = new RemoteRepository();

    Observable<String> stringObservable = Observable.defer(new Callable<ObservableSource<? extends String>>() {
        @Override
        public ObservableSource<? extends String> call() throws Exception {
            return remoteRepo.fetchMovies();
        }
    });

    io.reactivex.Observer<String> stringObserver = new io.reactivex.Observer<String>() {
        @Override
        public void onSubscribe(Disposable d) {
            // show loading dialog
        }

        @Override
        public void onNext(String response) {
            // Local repository update can be done here
            localRepo.insert(response);
        }

        @Override
        public void onError(Throwable e) {
            // Handle the Error(Exception thrown)
        }

        @Override
        public void onComplete() {

        }
    };

    stringObservable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(stringObserver);
    }

   void createObserver() {
        localViewModel.getLiveDataVariable().observe(this, new Observer<String>() {
        @Override
        public void onChanged(@Nullable String response) {
              // Now this response contains the updated movies.
          }
    })
   }
}

LocalViewModel.class

class LocalViewModel {

   LocalRepository localRepo;
   LiveData<String> liveDataVariable;

   //constructor
   LocalViewMoel(Application application) {
      localRepo = new LocalRepository(application);
   }

   public LiveData<String> getLiveDataVariable() {
     if(liveDataVariable == null) {
        liveDataVariable = localRepo.queryToGetTheLiveDataVariable();
      }
      return liveDataVariable;
   }

}

RemoteRepository现在正在进行同步调用。如果响应成功,则返回字符串,否则抛出Error(Exception)

RemoteRepository.java

class RemoteRepository {

 public String fetchMovieFromRemote(int page, onSuccessListener successListener) throws Exception{
    okhttp3.Response response  movieService.fetchPopularMovies(ApiConstants.BASE_URL, page).execute();
    ResponseBody body = response.body();

    if (!response.isSuccessful()) {
        throw new Exception("An error occured");
    }

    String result = body.string();
    return result;

}

答案 1 :(得分:0)

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

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

enter image description here

如上面所见,您正在观察磁盘上的更改,并在更改时收到更新。

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

答案 2 :(得分:0)

视图模型和存储库之间的两种通信方式是

  1. 使用回调界面
  2. 使用转换。
  

Transformations具有类似于RxJava的map和switchMap运算符   可以将实时数据转换为其他数据

     

您还可以使用Mediator livedata创建自己的自定义   操作员。 MediatorLiveData用于观察多个实时数据   源并在那里进行更改。

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