Google Samples 学习之 todo-mvp(四)

本文着重对 addedittask 文件夹进行分析,其内容为添加任务或编辑任务信息功能模块的代码。 前置文章: Google Samples 学习之 todo-mvp(一) Google Samples 学习之 todo-mvp(二) Google Samples 学习之 todo-mvp(三)

结构分析

addedittask 目录的结构如下图所示:

dir.PNG

各个文件的职责和功能为:

  • AddEditTaskActivity 和 AddEditTaskFragment:组成本功能模块的 View 层代码
  • AddEditTaskContract:定义 Preseter 和 View 的功能与访问方法
  • AddEditTaskPresenter:本模块的 Presenter 层代码

代码分析

View 层接口方法:

  • showEmptyTaskError:显示空任务错误信息
  • showTasksList:但任务添加或修改完毕后,要显示任务列表
  • setTitle:设置要显示的任务标题
  • setDescription:设置要显示的任务描述
  • isActive:能否操作任务编辑界面

Presenter 层接口方法:

  • saveTask:保存任务信息
  • populateTask:从 Model 层获取任务数据
  • isDataMissing:任务信息是否丢失

实现接口 AddEditTaskContract.Presenter 和 TasksDataSource.GetTaskCallback。

  • mTasksRepository:Model 层访问引用
  • mAddTaskView:View 层访问引用
  • mTaskId:如果新建任务则值为 null,如果修改任务则为对应任务的 entryid
  • mIsDataMissing:绘制 UI 所需的数据是否丢失

如果要修改任务而且数据丢失,则调用 populateTask,否则任何工作都不做。 判断是否修改任务要调用 isNewTask 方法:实际就是判断 mTaskId 是否为 null。

添加任务要调用 createTask:

  1. 任务标题和描述都为空时,显示空任务错误信息,否则继续执行
  2. 储存信息,并且返回任务列表页面

修改任务调用 updateTask:

  1. 使用 saveTask 接口存储任务信息,来覆盖旧信息,防止缓存中没有相应任务导致空指针错误。
  2. 返回任务列表界面

注意:如果新建任务时调用了 updateTask 则要抛出 RuntimeException

使用 getTask 接口获取任务信息。 注意:如果新建任务时调用了 updateTask 则要抛出 RuntimeException。

返回 mIsDataMissing。 View 层会调用此方法,如果方法返回 true,则稍后 Presenter 会重新获取数据填充 UI 元素。

注意:如果不设置这个 public 方法,则在编辑任务时,如果 Android 设备发生配置变更时(如:横竖屏幕转换),不管数据是否保存在缓存中,View 层总会要求 Presenter 层重新获取数据,效率太低且用户体验极差,甚至会导致编辑内容丢失。

  1. 如果可以操作任务编辑界面(isActive 返回 true),则更新 UI(调用 setTitle 和 setDescription)
  2. mIsDataMissing 设为 false

如果可以操作任务编辑界面(isActive 返回 true),显示空任务错误信息

是负责连接 View 和 Presenter 功能的注意代码,再次生成相应的 Presenter 实例。 下面对其中的重要代码简要分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
    actionBar.setTitle(R.string.edit_task);
    Bundle bundle = new Bundle();
    bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
    addEditTaskFragment.setArguments(bundle);
} else {
    actionBar.setTitle(R.string.add_task);
}
...

注意实际的 View 层的绘制界面的功能主要靠 fragment 实现,所以要把相应的参数以 fragment arguments 的形式传入内部的 fragment。 而且,本模块涉及添加和修改任务两个功能,分辨它们的方式是:在传给 activity 的 intent 里包含有任务 id 的 extra 信息,那么这个 activity 就负责显示修改任务的界面,否则为新建任务界面。 另外,不要忘了对不同的功能界面调用 ActionBar 的 setTitle 显示不同的标题,方便用户使用。

1
2
3
4
5
    boolean shouldLoadDataFromRepo = true;

    if (savedInstanceState != null) {
        shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY);
    }

这段代码和方法 onSaveInstanceState 配合,优化配置改变时的恢复功能,了解是否需要重新获取数据。

1
2
3
4
5
6
7
mAddEditTaskPresenter = new AddEditTaskPresenter(
        taskId,
        Injection.provideTasksRepository(getApplicationContext()),
        addEditTaskFragment,
        shouldLoadDataFromRepo);

addEditTaskFragment.setPresenter(mAddEditTaskPresenter);

这里的代码实际生成了 Presenter 实例,但是必须注意 fragment 才会去实现 AddEditTaskContract.View 接口,activity 只起到连接作用,所以是调用 fragment 的 setPresenter 方法传入 Presenter 实例。 这里的 Injcetion 类是手动实现了依赖注入方法,提供 Model 层的操作类实例,而且根据 gradle 的 build flavor 的不同,还有不同的注入方式——测试环境下注入的是 mock 要用到的模拟类。

从 Presenter 中获取数据是否丢失的信号(调用 isDataMissing 方法),然后保存它的状态,防止配置变更的时候进行多余的数据获取操作。

因为在 onCreate 中调用 setDisplayHomeAsUpEnabled(true) 设置菜单的 home 按钮为向上一级页面跳转,必须重新这个方法保证保证带有 fragment 的 activity 页面正确跳转。

注意在生命周期的 resume 阶段 Presenter 必须重新初始化,但是否从 Model 获取资源则由原有 Presenter 实例的 mIsDataMissing 字段的值决定(也必须是修改任务信息时)。

注意 fragment 中要让菜单的设置生效需要加入如下语句:

1
setHasOptionsMenu(true);

在这个 app 里,都是从任务列表界面进入任务新建或修改界面,所以可以使用 finish 方法退回到原有的任务列表界面。

另外,因为这个 app 较为简单,所以直接在此处设置 activity 的返回结果,逻辑上比较清晰,省去了在 activity 中较为复杂的逻辑判断。

缺点就是,fragment 实际上假定了管理它的 activity 的部分行为,如果将来要重用这个 fragment 的代码可能会遇到困难。如果确实有必要解耦 activity 和 fragment,实现 fragment 复用,则可以使用回调实现结果设置。

在 fragment 被添加到 activity 中,界面元素就可以操作了,因而调用 Fragment 的 isAdded 方法实现。

参考文章

Android ActionBar应用一:ActionBar返回任意页面和顶部搜索栏实现

android: 在fragment中添加actionbar, menu