前一篇我们主要介绍了MVP中View与Presenter的概念及交互,View层持有Presenter的实例,将一切逻辑操作都转发给Presenter层来操作,Presenter层持有View层的实例,并且将有关界面的部分的操作转发给View层实例操作,而设计到数据的部分则和Model层一起来操作。根据google官方的MVP项目例子todoapp,我们还分别介绍了Activity的作用,View层的具体角色,Presenter的角色以及Contract契约类的作用。这一篇我们来介绍有关Model层的设计。
MVP中Presenter与Model层要解决的主要问题有以下几点:
1 数据存储,获取,以及合法性校验等问题
2 数据的状态变化,需要通知上层等问题
3 数据的管理,如存储本地还是内存,获取数据来源,是从本地还是网络等
4 数据的加工,组装,比如将一个接口的response到的数据加工成另一个接口需要的request参数问题等
一般来说,Model与Presenter需要处理的问题就这些,一般Model层的设计都需要分层进行处理,每一层规划好自己的职责,对外提供统一的接口。关于Model层的设计与Presenter的职责划分是很有大的文章可做的。例如官方的MVP架构就有几种不同的实现,我们先来看一种最简单的实现。
我们先来看todoapp的架构,如下:
可以看到,Model层主要由两部分构成,第一层就是Respository,第二层主要是输出存储层,包括local与remote,这样数据的同一由Respository管理,对于Presenter来说,我只与Respository交互,至于数据怎么来,我不关心,实际中和代码也是如此:
我们还是以Tasks模块TasksPresenter为例,详细讲一下Presenter与Model层的交互。TasksPresenter源代码如下:
package com.example.android.architecture.blueprints.todoapp.tasks;
import android.app.Activity;
import android.support.annotation.NonNull;
import com.example.android.architecture.blueprints.todoapp.addedittask.AddEditTaskActivity;
import com.example.android.architecture.blueprints.todoapp.data.Task;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository;
import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingResource;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the
* UI as required.
*/
public class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;
private boolean mFirstLoad = true;
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
}
@Override
public void start() {
loadTasks(false);
}
@Override
public void result(int requestCode, int resultCode) {
// If a task was successfully added, show snackbar
if (AddEditTaskActivity.REQUEST_ADD_TASK == requestCode && Activity.RESULT_OK == resultCode) {
mTasksView.showSuccessfullySavedMessage();
}
}
@Override
public void loadTasks(boolean forceUpdate) {
// Simplification for sample: a network reload will be forced on first load.
loadTasks(forceUpdate || mFirstLoad, true);
mFirstLoad = false;
}
/**
* @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource}
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
if (forceUpdate) {
mTasksRepository.refreshTasks();
}
// The network request might be handled in a different thread so make sure Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
List tasksToShow = new ArrayList();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
EspressoIdlingResource.decrement(); // Set app as idle.
}
// We filter the tasks based on the requestType
for (Task task : tasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToShow.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToShow.add(task);
}
break;
default:
tasksToShow.add(task);
break;
}
}
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasksToShow);
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
mTasksView.showLoadingTasksError();
}
});
}
private void processTasks(List tasks) {
if (tasks.isEmpty()) {
// Show a message indicating there are no tasks for that filter type.
processEmptyTasks();
} else {
// Show the list of tasks
mTasksView.showTasks(tasks);
// Set the filter label's text.
showFilterLabel();
}
}
private void showFilterLabel() {
switch (mCurrentFiltering) {
case ACTIVE_TASKS:
mTasksView.showActiveFilterLabel();
break;
case COMPLETED_TASKS:
mTasksView.showCompletedFilterLabel();
break;
default:
mTasksView.showAllFilterLabel();
break;
}
}
private void processEmptyTasks() {
switch (mCurrentFiltering) {
case ACTIVE_TASKS:
mTasksView.showNoActiveTasks();
break;
case COMPLETED_TASKS:
mTasksView.showNoCompletedTasks();
break;
default:
mTasksView.showNoTasks();
break;
}
}
@Override
public void addNewTask() {
mTasksView.showAddTask();
}
@Override
public void openTaskDetails(@NonNull Task requestedTask) {
checkNotNull(requestedTask, "requestedTask cannot be null!");
mTasksView.showTaskDetailsUi(requestedTask.getId());
}
@Override
public void completeTask(@NonNull Task completedTask) {
checkNotNull(completedTask, "completedTask cannot be null!");
mTasksRepository.completeTask(completedTask);
mTasksView.showTaskMarkedComplete();
loadTasks(false, false);
}
@Override
public void activateTask(@NonNull Task activeTask) {
checkNotNull(activeTask, "activeTask cannot be null!");
mTasksRepository.activateTask(activeTask);
mTasksView.showTaskMarkedActive();
loadTasks(false, false);
}
@Override
public void clearCompletedTasks() {
mTasksRepository.clearCompletedTasks();
mTasksView.showCompletedTasksCleared();
loadTasks(false, false);
}
/**
* Sets the current task filtering type.
*
* @param requestType Can be {@link TasksFilterType#ALL_TASKS},
* {@link TasksFilterType#COMPLETED_TASKS}, or
* {@link TasksFilterType#ACTIVE_TASKS}
*/
@Override
public void setFiltering(TasksFilterType requestType) {
mCurrentFiltering = requestType;
}
@Override
public TasksFilterType getFiltering() {
return mCurrentFiltering;
}
}
我们看到TasksPresenter引用了Respository实例对象mTasksRepository,并且在构造方法中初始化了该对象,我们以loadTasks()为例来分析一下task是如何加载的。首先loadTasks最终会调用到TasksPresenter的私有方法loadTasks(boolean forceUpdate, final boolean showLoadingUI),源码如下:
/**
* @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource}
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
if (forceUpdate) {
mTasksRepository.refreshTasks();
}
// The network request might be handled in a different thread so make sure Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
List tasksToShow = new ArrayList();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
EspressoIdlingResource.decrement(); // Set app as idle.
}
// We filter the tasks based on the requestType
for (Task task : tasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToShow.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToShow.add(task);
}
break;
default:
tasksToShow.add(task);
break;
}
}
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasksToShow);
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
mTasksView.showLoadingTasksError();
}
});
}
private void processTasks(List tasks) {
if (tasks.isEmpty()) {
// Show a message indicating there are no tasks for that filter type.
processEmptyTasks();
} else {
// Show the list of tasks
mTasksView.showTasks(tasks);
// Set the filter label's text.
showFilterLabel();
}
}
loadTasks(boolean forceUpdate, final boolean showLoadingUI)会根据参数决定是否刷新,接着会调用mTasksRepository.getTasks()传入一个TasksDataSource.LoadTasksCallback回调来加载所有的task,这样Presenter层的TasksPresenter就拿到了mTasksRepository中所有的task数据。
可以看到TaskPresenter层中对Model层数据的操作是通过TasksRepository提供的接口getTasks()来实现的,不会直接去查询数据库或者发起网络请求等。
那么TasksRepository中getTasks又是如何实现的呢?由于TaskRespository实现了TasksDataSource接口,我们来看这两个类的实现
package com.example.android.architecture.blueprints.todoapp.data.source;
import android.support.annotation.NonNull;
import com.example.android.architecture.blueprints.todoapp.data.Task;
import java.util.List;
/**
* Main entry point for accessing tasks data.
*
* For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other
* methods to inform the user of network/database errors or successful operations.
* For example, when a new task is created, it's synchronously stored in cache but usually every
* operation on database or network should be executed in a different thread.
*/
public interface TasksDataSource {
interface LoadTasksCallback {
void onTasksLoaded(List tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
void getTasks(@NonNull LoadTasksCallback callback);
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
void saveTask(@NonNull Task task);
void completeTask(@NonNull Task task);
void completeTask(@NonNull String taskId);
void activateTask(@NonNull Task task);
void activateTask(@NonNull String taskId);
void clearCompletedTasks();
void refreshTasks();
void deleteAllTasks();
void deleteTask(@NonNull String taskId);
}
package com.example.android.architecture.blueprints.todoapp.data.source;
import static com.google.common.base.Preconditions.checkNotNull;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.example.android.architecture.blueprints.todoapp.data.Task;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Concrete implementation to load tasks from the data sources into a cache.
*
* For simplicity, this implements a dumb synchronisation between locally persisted data and data
* obtained from the server, by using the remote data source only if the local database doesn't
* exist or is empty.
*/
public class TasksRepository implements TasksDataSource {
private static TasksRepository INSTANCE = null;
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
/**
* This variable has package local visibility so it can be accessed from tests.
*/
Map mCachedTasks;
/**
* Marks the cache as invalid, to force an update the next time data is requested. This variable
* has package local visibility so it can be accessed from tests.
*/
boolean mCacheIsDirty = false;
// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
/**
* Returns the single instance of this class, creating it if necessary.
*
* @param tasksRemoteDataSource the backend data source
* @param tasksLocalDataSource the device storage data source
* @return the {@link TasksRepository} instance
*/
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}
/**
* Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance
* next time it's called.
*/
public static void destroyInstance() {
INSTANCE = null;
}
/**
* Gets tasks from cache, local data source (SQLite) or remote data source, whichever is
* available first.
*
* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to
* get the data.
*/
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
}
@Override
public void completeTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.completeTask(task);
mTasksLocalDataSource.completeTask(task);
Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), completedTask);
}
@Override
public void completeTask(@NonNull String taskId) {
checkNotNull(taskId);
completeTask(getTaskWithId(taskId));
}
@Override
public void activateTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.activateTask(task);
mTasksLocalDataSource.activateTask(task);
Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), activeTask);
}
@Override
public void activateTask(@NonNull String taskId) {
checkNotNull(taskId);
activateTask(getTaskWithId(taskId));
}
@Override
public void clearCompletedTasks() {
mTasksRemoteDataSource.clearCompletedTasks();
mTasksLocalDataSource.clearCompletedTasks();
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
Iterator> it = mCachedTasks.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
if (entry.getValue().isCompleted()) {
it.remove();
}
}
}
/**
* Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
* uses the network data source. This is done to simplify the sample.
*
* Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to
* get the data.
*/
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);
Task cachedTask = getTaskWithId(taskId);
// Respond immediately with cache if available
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}
// Load from server/persisted if needed.
// Is the task in the local data source? If not, query the network.
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
@Override
public void refreshTasks() {
mCacheIsDirty = true;
}
@Override
public void deleteAllTasks() {
mTasksRemoteDataSource.deleteAllTasks();
mTasksLocalDataSource.deleteAllTasks();
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();
}
@Override
public void deleteTask(@NonNull String taskId) {
mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
mTasksLocalDataSource.deleteTask(checkNotNull(taskId));
mCachedTasks.remove(taskId);
}
private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
refreshCache(tasks);
refreshLocalDataSource(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
private void refreshCache(List tasks) {
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();
for (Task task : tasks) {
mCachedTasks.put(task.getId(), task);
}
mCacheIsDirty = false;
}
private void refreshLocalDataSource(List tasks) {
mTasksLocalDataSource.deleteAllTasks();
for (Task task : tasks) {
mTasksLocalDataSource.saveTask(task);
}
}
@Nullable
private Task getTaskWithId(@NonNull String id) {
checkNotNull(id);
if (mCachedTasks == null || mCachedTasks.isEmpty()) {
return null;
} else {
return mCachedTasks.get(id);
}
}
}
TasksDataSource定义了很多对task的操作,例如getTasks(),getTask(),saveTask()等,这些都是Model对数据的操作,而TaskRespository是对这些接口的实现,我们来看getTasks的实现。
/**
* This variable has package local visibility so it can be accessed from tests.
*/
Map mCachedTasks;
/**
* Marks the cache as invalid, to force an update the next time data is requested. This variable
* has package local visibility so it can be accessed from tests.
*/
boolean mCacheIsDirty = false;
/**
* Gets tasks from cache, local data source (SQLite) or remote data source, whichever is
* available first.
*
* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to
* get the data.
*/
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
getTasks()的设计很简单,首先判断缓存mCachedTasks 是否有数据,并且数据是否有效,这里用一个变量mCacheIsDirty来表示,如果满足条件就直接将缓存的数据传入回调。如果没有缓存,则如果缓存的数据无效,则该通过remote来更新数据了,否则就交给本地区获取数据。
我们分别来看remote与local获取数据。
先来看remote的获取数据:
private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List tasks) {
refreshCache(tasks);
refreshLocalDataSource(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
可以看到,直接调用了mTasksRemoteDataSource的getTasks来获取数据,并传入回调。而mTasksRemoteDataSource的初始化最终赋值是FakeTasksRemoteDataSource对象,因此最终调用到了FakeTasksRemoteDataSource的getTasks()。代码如下:
/**
* Implementation of a remote data source with static access to the data for easy testing.
*/
public class FakeTasksRemoteDataSource implements TasksDataSource {
private static FakeTasksRemoteDataSource INSTANCE;
private static final Map TASKS_SERVICE_DATA = new LinkedHashMap<>();
// Prevent direct instantiation.
private FakeTasksRemoteDataSource() {}
public static FakeTasksRemoteDataSource getInstance() {
if (INSTANCE == null) {
INSTANCE = new FakeTasksRemoteDataSource();
}
return INSTANCE;
}
@Override
public void getTasks(@NonNull LoadTasksCallback callback) {
callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));
}
......
@Override
public void saveTask(@NonNull Task task) {
TASKS_SERVICE_DATA.put(task.getId(), task);
}
......
}
可以看到,直接是FakeTasksRemoteDataSource 的TASKS_SERVICE_DATA 中的数据。而在saveTask中时,又将数据存储进去了。
接着我们分析local端数据的获取
mTasksLocalDataSource的getTask调用最终会调用到TasksLocalDataSource中的getTasks(),代码如下:
/**
* Concrete implementation of a data source as a db.
*/
public class TasksLocalDataSource implements TasksDataSource {
private static TasksLocalDataSource INSTANCE;
private TasksDbHelper mDbHelper;
// Prevent direct instantiation.
private TasksLocalDataSource(@NonNull Context context) {
checkNotNull(context);
mDbHelper = new TasksDbHelper(context);
}
public static TasksLocalDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
INSTANCE = new TasksLocalDataSource(context);
}
return INSTANCE;
}
/**
* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesn't exist
* or the table is empty.
*/
@Override
public void getTasks(@NonNull LoadTasksCallback callback) {
List tasks = new ArrayList();
SQLiteDatabase db = mDbHelper.getReadableDatabase();
String[] projection = {
TaskEntry.COLUMN_NAME_ENTRY_ID,
TaskEntry.COLUMN_NAME_TITLE,
TaskEntry.COLUMN_NAME_DESCRIPTION,
TaskEntry.COLUMN_NAME_COMPLETED
};
Cursor c = db.query(
TaskEntry.TABLE_NAME, projection, null, null, null, null, null);
if (c != null && c.getCount() > 0) {
while (c.moveToNext()) {
String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID));
String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE));
String description =
c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION));
boolean completed =
c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1;
Task task = new Task(title, description, itemId, completed);
tasks.add(task);
}
}
if (c != null) {
c.close();
}
db.close();
if (tasks.isEmpty()) {
// This will be called if the table is new or just empty.
callback.onDataNotAvailable();
} else {
callback.onTasksLoaded(tasks);
}
}
......
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
SQLiteDatabase db = mDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId());
values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle());
values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription());
values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted());
db.insert(TaskEntry.TABLE_NAME, null, values);
db.close();
}
......
}
可以看到,local端数据的获取就是直接查询的数据库,关于数据库的操作这里就不再分析了。
总结一下,todoapp的Presenter层的loadTasks()最终会将获取数据的操作转发给TaskRespository,而Taskrespository会根据实际情况去获取缓存,remote或者local 数据库中的数据,最终将获取的tasks传递给回调。从而将数据获取到。
以上就是关于从Presenter获取Model层的数据的操作,暂时还没发现Model层变化如下通知Presenter层的情况,后面再做分析
todo-mvp-loaders 用Loader取数据的MVP。基于上一个例子todo-mvp, 只不过这里改为用Loader来从Repository得到数据,结构如下:
使用Loader的优势:
Diff with todo-mvp
既然是基于todo-mvp, 那么之前说过的那些就不再重复, 我们来看一下都有什么改动:
git difftool -d todo-mvp
添加了两个类:
TaskLoader和TasksLoader.
在Activity中new Loader类, 然后传入Presenter的构造方法.
Contract中View接口删掉了isActive()方法, Presenter删掉了populateTask()方法.
数据获取
添加的两个新类是TaskLoader和TasksLoader, 都继承于AsyncTaskLoader, 只不过数据的类型一个是单数, 一个是复数.
AsyncTaskLoader是基于ModernAsyncTask, 类似于AsyncTask,
把load数据的操作放在loadInBackground()里即可, deliverResult()方法会将结果返回到主线程, 我们在listener的onLoadFinished()里面就可以接到返回的数据了, (在这个例子中是几个Presenter实现了这个接口).
TasksDataSource接口的这两个方法:
List<Task> getTasks();
Task getTask(@NonNull String taskId);
都变成了同步方法, 因为它们是在loadInBackground()方法里被调用.
Presenter中保存了Loader和LoaderManager, 在start()方法里initLoader, 然后onCreateLoader返回构造传入的那个loader.
onLoadFinished()里面调用View的方法. 此时Presenter实现LoaderManager.LoaderCallbacks.
数据改变监听
TasksRepository类中定义了observer的接口, 保存了一个listener的list:
private List mObservers = new ArrayList();
public interface TasksRepositoryObserver {
void onTasksChanged();
}
每次有数据改动需要刷新UI时就调用:
private void notifyContentObserver() {
for (TasksRepositoryObserver observer : mObservers) {
observer.onTasksChanged();
}
}
在两个Loader里注册和注销自己为TasksRepository的listener: 在onStartLoading()里add, onReset()里面remove方法.
这样每次TasksRepository有数据变化, 作为listener的两个Loader都会收到通知, 然后force load:
@Override
public void onTasksChanged() {
if (isStarted()) {
forceLoad();
}
}
这样onLoadFinished()方法就会被调用.
欢迎访问我的github
代码下载