Google官方MVP示例分析

引言

在我做一些MVP的设计模式的时候,我通常从三个方面去做:

  • 从《Android设计模式》这本书中学习如何去写
  • 看一些博客
  • 看官方文档

但这些中,我认为去看官方文档是十分重要的,因为每个人的代码风格不同,规范也各不相同。我们在一开始学习的时候,我认为必须要去多看一些官方给出的例子和文档。从博客和书本中学习一个知识的理解,从官方文档和例子来学习如何去写出一个好的代码。

所以在MVP模式的学习中,我想多分析一下Google官方的MVP的示例。

Github地址:https://github.com/android/architecture-samples

这篇博客从官方示例入手,结合Android设计模式和一些博客来说明如何去写出一个好看漂亮的

项目层包

img

项目效果图:

img

我们先从addedittask来看

addedittask

img

其实在这里我们可以看到MVP的一个模式,在Contract中去写VIew和Presenter的接口,在TaskPresenter中去实现Contract.Presenter接口,在Fragment中去实现Contract.View的接口

我们先来看看《Android设计模式》对于MVP的解释:

“MVP全程Model View Presenter。对于一个可扩展、稳定的应用来说,我们需要定义分离各个层,主要是UI层、业务逻辑层、数据层。毕竟我们不知道以后还要加入什么逻辑,是从本地数据库检索数据?还是从远程的服务器中?我们的UI、数据库是否会被替换?例如,随着产品的升级,我们的UI可能会被重新设计。”

“Presenter-交互中间人:作为沟通View和Model的桥梁,从Model层检索数据后,返回给View层,使得View和Model之间没有耦合,也从业务逻辑从View角色上抽离出来”

“View-用户界面:View通常是指Activity,Fragment或者某个View控件,它含有 一个Presenter成员变量。通常View需要实现一个逻辑接口,将View上的操作通过会转交Presenter进行实现,最后,Presenter调用View逻辑接口”

“Model-数据的存取:对于一个结构化的APP来说,Model角色主要是提供数据的存取功能。Presenter需要通过Model层存取、获取数据,Model就像一个数据仓库。更直白的说,Model是封装了数据库DAO或者网络获取数据的角色,或者是集合”

从以上的三段话,我们大致了解了MVP的一个设计模式。现在我们来分析一下在addedittask包中的MVP

我们先来看看Contract接口

AddEditTaskContract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface AddEditTaskContract {

interface View extends BaseView<Presenter> {//BaseView是一个公共的View接口

void showEmptyTaskError();//这里展示的是当addTask为空时候的View反应

void showTasksList();//这里展示的是Task的一个列表

void setTitle(String title);//这里展示的是Task的标题,在后面我们可以看到是在Presenter中set

void setDescription(String description);//这里展示的是Task的描述,同上

boolean isActive();//
}

interface Presenter extends BasePresenter {//BasePresenter是一个公共的Presenter接口

void saveTask(String title, String description);//在Fragment调用了这个方法去保存创建的Task

void populateTask();//在订阅的时候调用这个方法,在里面实现RXJAVA处理数据

boolean isDataMissing();//
}
}

我在官方的例子中加入了我自己理解的注释,再根据《Android设计模式》的理解,我想我们对这个Contract已经有了一个很好的理解。

一开始我很不能理解,为什么这些方法的重写都写在了Fragment中,其实就是UI与View交互,而我们通过View与Presenter交互,然后让Presenter来处理逻辑,最后给到我们View 最后更新UI。其实就是这样的一个逻辑

在说了这个接口的时候,我们还没有去说一下公共的BaseView和BasePresenter

BaseVIew、BasePresenter

1
2
3
4
5
6
7
8
9
10
11
12
public interface BasePresenter {

void subscribe();

void unsubscribe();

}
public interface BaseView<T> {

void setPresenter(T presenter);

}

在这里我相信不用过多的去解释这个事情,我们在基础的Presenter中建立了订阅和取消订阅的一个方法,在基础的View通过一个set来传入Presenter

AddEditTaskPresenter

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
* the UI as required.
*/
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter {

@NonNull
private final TasksDataSource mTasksRepository;

@NonNull
private final AddEditTaskContract.View mAddTaskView;

@NonNull
private final BaseSchedulerProvider mSchedulerProvider;

@Nullable
private String mTaskId;

private boolean mIsDataMissing;

@NonNull
private CompositeDisposable mCompositeDisposable;
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
* @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo,
@NonNull BaseSchedulerProvider schedulerProvider) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;

mSchedulerProvider = checkNotNull(schedulerProvider, "schedulerProvider cannot be null!");

mCompositeDisposable = new CompositeDisposable();
mAddTaskView.setPresenter(this);
}

@Override
public void subscribe() {
if (!isNewTask() && mIsDataMissing) {
populateTask();
}
}

@Override
public void unsubscribe() {
mCompositeDisposable.clear();
}

@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}

@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mCompositeDisposable.add(mTasksRepository
.getTask(mTaskId)
.subscribeOn(mSchedulerProvider.computation())
.observeOn(mSchedulerProvider.ui())
.subscribe(
// onNext
taskOptional -> {
if (taskOptional.isPresent()) {
Task task = taskOptional.get();
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());

mIsDataMissing = false;
}
} else {
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
},
// onError
throwable -> {
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}));
}

@Override
public boolean isDataMissing() {
return mIsDataMissing;
}

private boolean isNewTask() {
return mTaskId == null;
}

private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mTasksRepository.saveTask(newTask);
mAddTaskView.showTasksList();
}
}

private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
mTasksRepository.saveTask(new Task(title, description, mTaskId));
mAddTaskView.showTasksList(); // After an edit, go back to the list.
}
}

有必要说这里加入了Rxjava来订阅。简单的说Rxjava就是一个在不同的线程中处理不同的事情,例如在IO线程中处理网络请求,在主线程中去更新UI。

我们可以看到,这是一个Presenter,实现了Contract.Presenter接口

所以后面的populateTask、saveTask是Contract.Presenter接口的方法。而subscribe和unsubscribe是BasePresenter的方法

订阅的时候去调用populateTask方法,而populateTask是利用Lambda表达式、Rxjava的链式调用。

TasksDataSource是用来存放Task的数据的地方,在这里TasksDataSource是本地和存储在远程的数据

我们接下来还会分析这个类。

在线程管理的时候,我们创建了一个线程的类,来统一管理线程。所以mSchedulerProvider是一个控制线程的一个类对象

在这里的这些引用类型都是不能改变引用的,因为我们用到了final关键字,证明是不可以被赋值。(PS:如果是类用final修饰,则证明这个类不可被继承)