Google Samples 学习之 todo-mvp(四)
本文着重对 addedittask 文件夹进行分析,其内容为添加任务或编辑任务信息功能模块的代码。 前置文章: Google Samples 学习之 todo-mvp(一) Google Samples 学习之 todo-mvp(二) Google Samples 学习之 todo-mvp(三)
结构分析
addedittask 目录的结构如下图所示:
各个文件的职责和功能为:
- AddEditTaskActivity 和 AddEditTaskFragment:组成本功能模块的 View 层代码
- AddEditTaskContract:定义 Preseter 和 View 的功能与访问方法
- AddEditTaskPresenter:本模块的 Presenter 层代码
代码分析
AddEditTaskContract
View 层接口方法:
- showEmptyTaskError:显示空任务错误信息
- showTasksList:但任务添加或修改完毕后,要显示任务列表
- setTitle:设置要显示的任务标题
- setDescription:设置要显示的任务描述
- isActive:能否操作任务编辑界面
Presenter 层接口方法:
- saveTask:保存任务信息
- populateTask:从 Model 层获取任务数据
- isDataMissing:任务信息是否丢失
AddEditTaskPresenter
实现接口 AddEditTaskContract.Presenter 和 TasksDataSource.GetTaskCallback。
字段
- mTasksRepository:Model 层访问引用
- mAddTaskView:View 层访问引用
- mTaskId:如果新建任务则值为 null,如果修改任务则为对应任务的 entryid
- mIsDataMissing:绘制 UI 所需的数据是否丢失
start
如果要修改任务而且数据丢失,则调用 populateTask,否则任何工作都不做。 判断是否修改任务要调用 isNewTask 方法:实际就是判断 mTaskId 是否为 null。
saveTask
添加任务要调用 createTask:
- 任务标题和描述都为空时,显示空任务错误信息,否则继续执行
- 储存信息,并且返回任务列表页面
修改任务调用 updateTask:
- 使用 saveTask 接口存储任务信息,来覆盖旧信息,防止缓存中没有相应任务导致空指针错误。
- 返回任务列表界面
注意:如果新建任务时调用了 updateTask 则要抛出 RuntimeException
populateTask
使用 getTask 接口获取任务信息。 注意:如果新建任务时调用了 updateTask 则要抛出 RuntimeException。
isDataMissing
返回 mIsDataMissing。 View 层会调用此方法,如果方法返回 true,则稍后 Presenter 会重新获取数据填充 UI 元素。
注意:如果不设置这个 public 方法,则在编辑任务时,如果 Android 设备发生配置变更时(如:横竖屏幕转换),不管数据是否保存在缓存中,View 层总会要求 Presenter 层重新获取数据,效率太低且用户体验极差,甚至会导致编辑内容丢失。
onTaskLoaded
- 如果可以操作任务编辑界面(isActive 返回 true),则更新 UI(调用 setTitle 和 setDescription)
- mIsDataMissing 设为 false
onDataNotAvailable
如果可以操作任务编辑界面(isActive 返回 true),显示空任务错误信息
AddEditTaskActivity
onCreate
是负责连接 View 和 Presenter 功能的注意代码,再次生成相应的 Presenter 实例。 下面对其中的重要代码简要分析:
|
|
注意实际的 View 层的绘制界面的功能主要靠 fragment 实现,所以要把相应的参数以 fragment arguments 的形式传入内部的 fragment。 而且,本模块涉及添加和修改任务两个功能,分辨它们的方式是:在传给 activity 的 intent 里包含有任务 id 的 extra 信息,那么这个 activity 就负责显示修改任务的界面,否则为新建任务界面。 另外,不要忘了对不同的功能界面调用 ActionBar 的 setTitle 显示不同的标题,方便用户使用。
|
|
这段代码和方法 onSaveInstanceState 配合,优化配置改变时的恢复功能,了解是否需要重新获取数据。
|
|
这里的代码实际生成了 Presenter 实例,但是必须注意 fragment 才会去实现 AddEditTaskContract.View 接口,activity 只起到连接作用,所以是调用 fragment 的 setPresenter 方法传入 Presenter 实例。 这里的 Injcetion 类是手动实现了依赖注入方法,提供 Model 层的操作类实例,而且根据 gradle 的 build flavor 的不同,还有不同的注入方式——测试环境下注入的是 mock 要用到的模拟类。
onSaveInstanceState
从 Presenter 中获取数据是否丢失的信号(调用 isDataMissing 方法),然后保存它的状态,防止配置变更的时候进行多余的数据获取操作。
onSupportNavigateUp
因为在 onCreate 中调用 setDisplayHomeAsUpEnabled(true) 设置菜单的 home 按钮为向上一级页面跳转,必须重新这个方法保证保证带有 fragment 的 activity 页面正确跳转。
AddEditTaskFragment
onResume
注意在生命周期的 resume 阶段 Presenter 必须重新初始化,但是否从 Model 获取资源则由原有 Presenter 实例的 mIsDataMissing 字段的值决定(也必须是修改任务信息时)。
onCreateView
注意 fragment 中要让菜单的设置生效需要加入如下语句:
|
|
showTasksList
在这个 app 里,都是从任务列表界面进入任务新建或修改界面,所以可以使用 finish 方法退回到原有的任务列表界面。
另外,因为这个 app 较为简单,所以直接在此处设置 activity 的返回结果,逻辑上比较清晰,省去了在 activity 中较为复杂的逻辑判断。
缺点就是,fragment 实际上假定了管理它的 activity 的部分行为,如果将来要重用这个 fragment 的代码可能会遇到困难。如果确实有必要解耦 activity 和 fragment,实现 fragment 复用,则可以使用回调实现结果设置。
isActive
在 fragment 被添加到 activity 中,界面元素就可以操作了,因而调用 Fragment 的 isAdded 方法实现。