Wen

Android MVP学习笔记 (中) - Google todo-mvp Demo

对于 Android 上 MVP 的实现,Google 还是做了一些整理和总结的,并且给出了一些代码Demo,放在Android Architecture Blueprints内,这次看其中最基本的例子:TODO-MVP 为了防止遗忘上一篇的内容,先来复习一下之前对MVP最重要的两点总结:

  1. View不直接与Model交互,而是通过与Presenter交互来与Model间接交互
  2. Presenter与View的交互是通过接口来进行的

App功能和代码结构

这是一个基本的Todo List App,但功能写的很完整。它主要有三个页面,下面是截图和对应的Activity:

Task列表页 (TasksActivity) Task细节页 (TaskDetailActivity) Task添加和编辑页 (AddEditTaskActivity)
  • Task列表页 (TasksActivity):App首页,显示所有task标题及其状态 (active or completed)
  • Task细节页 (TaskDetailActivity):从Task列表页点击任一task进入,可看到task的note
  • Task添加和编辑页 (AddEditTaskActivity):从Task列表页点击添加按钮,或者从Task细节页点击编辑按钮进入,可修改Task标题和note

其实还有一个统计页面 (StatisticsActivity),因为不是重点这里就不看了。下面是该项目的文件结构。

可以看到,每个页面都对应了一个文件夹,里面主要有四个类:

  • Activity:本例中activity不是MVP里的V,它只负责把ViewPresenter关联起来。
  • Fragment:MVP里的V
  • Contract:定义了View和Presenter的interface
  • Presenter: MVP里的P

每个页面都有自己的View和Presenter,但它们共同share一个Model,就在data那个folder里。下面选取其中最主要的TasksActivity来看看Google是如何实践MVP的。

TasksActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TasksActivity extends AppCompatActivity {
private TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
}
...
}

可以看到Activity做的事情有两件:

  • 创建Fragment,也就是View,注意在这里并没有把Presenter传给View,这是和上一个Demo不一样的地方。
  • 创建Presenter,构造函数中两个参数,第一个是Model (后面讲),第二个就是View了。

按照我们的学习,View和Presenter是相互引用的,但在Activity里并没有这个逻辑,其实这个逻辑定义在Presenter里。

Presenter

1
2
3
4
5
6
7
8
9
10
11
12
public class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
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);
}
...
}

mTasksView.setPresenter(this)就把Presenter传给了View,这样Presenter和View就建立里双向的联系。之所以这样做而不像上一个demo那样两者都在构造函数中互建联系,是因为本例中Presenter不是在View中创建的,而是和View一样在Activity中创建的,两者的创建有先后,在第一个创建的时候是没有办法把还没创建的第二个传进去的。

Presenter其他的逻辑下面讲。

View

View的代码略长,总结一下其中调用Presenter的地方其实只有一类:View里面几乎所有的事件回调函数都是在调用Presenter,而且只调用Presenter。举几处栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TasksFragment extends Fragment implements TasksContract.View {
...
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mPresenter.result(requestCode, resultCode);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mPresenter.loadTasks(false);
}
});
}
...
}

看到这里我会想一个问题,Presenter要怎么抽象?View比较简单,所有View相关的操作,Model也一样,所有data相关的操作,但在Presenter的interface中,要定义哪些接口呢?看看Demo中的Presenter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Presenter extends BasePresenter {
void result(int requestCode, int resultCode);
void loadTasks(boolean forceUpdate);
void addNewTask();
void openTaskDetails(@NonNull Task requestedTask);
void completeTask(@NonNull Task completedTask);
void activateTask(@NonNull Task activeTask);
void clearCompletedTasks();
void setFiltering(TasksFilterType requestType);
TasksFilterType getFiltering();
}

首先最明显的,View中需要的数据相关的操作都必须在Presenter中定义,比如loadTasks()completeTask()等。可是demo中还有很多接口是和数据无关的,比如result()openTaskDetails()等,接口的实现里面是直接调用的View里的函数,既然如此,为何view不直接调用而却要借助Presenter再反过来调用自己的函数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TasksPresenter implements TasksContract.Presenter {
...
@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 openTaskDetails(@NonNull Task requestedTask) {
checkNotNull(requestedTask, "requestedTask cannot be null!");
mTasksView.showTaskDetailsUi(requestedTask.getId());
}
}

我的理解是,作者是想分离视图逻辑和业务逻辑,把所有业务逻辑都放在Presenter中,所以View里面几乎所有的事件回调函数都是在调用Presenter,而且只调用Presenter,不管Presenter那个函数的实现是调用Model还是View自己。但konmik却在他的文章中有不同的观点:

When control goes from View to Presenter and then from Presenter to Model it is just a direct flow, it is easy to write code like this. You get an easy user -> view -> presenter -> model -> data sequence. But when control goes like this: user -> view -> presenter -> view -> presenter -> model -> data, it is just violates KISS principle. Don’t play ping-pong between your view and presenter.

Open Question. 这里只要搞清楚这两种处理方法就可以了。

Model

Demo中Model在理解MVP的目的下,并没有什么特别的,但作者的实现思路是非常practical的,值得学习,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TasksRepository implements TasksDataSource {
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
...
// Is the task in the local data source? If not, query the network.
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
...
@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);
}
});
}
});
}

这里的TasksRepository就是Model类,所有对data的操作,比如获取task,都会先看local有没有,如果没有再去remote的服务器尝试获取,一个真实的todo app就是应该这样做的,这个demo实现的方方面面都是教科书一般的标准。

MVP其他实现

本文只是介绍MVP最简单的实现方法,其实MVP还有很多open question和各种各样的实现思路,这里不再做具体的深入探究,感兴趣的推荐阅读《Android MVP 详解》

转载请注明出处:http://zhaowen.io/post/Android_MVP_Learning_Notes_2/