Android之官方MVP例子分析(第二篇addedittask包)

0、addedittask包,是新增/编辑页面

 

1、首先是interface的介绍,噢耶

/**
 * This specifies the contract between the view and the presenter.
 * 指定一个contract,里面有View接口、presenter接口
 */
public interface AddEditTaskContract {

    /**
     * 在AddEditTask页下的操作,会引起View的一些变化,对的,业务逻辑决定了View的变化,大牛太牛了
     */
    interface View extends BaseView<Presenter> {

        void showEmptyTaskError(); //界面上展示Task为Empty时的错误情况,当业务逻辑上,打开Task页后,如果Task挂了,View上要有展示

        void showTasksList(); //界面上展示Task列表吗?对的,就是这样的业务。View上就是把EditTask页面要关闭掉,就会展示TasksList页

        void setTitle(String title); //设置标题,业务逻辑上,不同的入口进来后,AddEditTask页面要根据是新建、还是编辑、展示对应的标题

        void setDescription(String description); //设置Task详情,在业务上,当进入AddEditTask页后,如果是编辑,View上需要展示要编辑的内容

        boolean isActive(); //判断当前的fragment是否已经依附到Activity上
    }

    /**
     *  具体的在AddEditTask下可以进行的动作或者行为,这些动作或者行为,会引起View的变化
     *  就是业务逻辑
     */
    interface Presenter extends BasePresenter {

        void saveTask(String title, String description); //在新增、修改页保存Task

        void populateTask(); // 填充Task详情,翻译的准确吗我?

        boolean isDataMissing(); //业务上,还要知道数据有没有意外丢失,比如进程被回收了
    }
}

 

2、然后介绍Activity吧

/**
 * Displays an add or edit task screen.
 * 哈哈,编辑Task页
 */
public class AddEditTaskActivity extends AppCompatActivity {

    public static final int REQUEST_ADD_TASK = 1; //请求码,呵呵,常量的干活

    public static final String SHOULD_LOAD_DATA_FROM_REPO_KEY = "SHOULD_LOAD_DATA_FROM_REPO_KEY"; //从Bundle里面恢复对象的时候,要用到的key

    private AddEditTaskPresenter mAddEditTaskPresenter; //Presenter的引用

    private ActionBar mActionBar; //View的引用,这里是ActionBar

    /**
     * 生命周期方法,老朋友了,牛逼
     * @param savedInstanceState 当Activity对象被意外销毁时,序列化的对象会存储在Bundle里,以再从Bundle里获取
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); //必须先call基类的onCreate方法
        setContentView(R.layout.addtask_act); //设置布局

        // Set up the toolbar. 初始化Toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mActionBar = getSupportActionBar();
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mActionBar.setDisplayShowHomeEnabled(true);

        AddEditTaskFragment addEditTaskFragment = (AddEditTaskFragment) getSupportFragmentManager()
                .findFragmentById(R.id.contentFrame); //通过id找fragment,醉了,大写F都不让用,艹

        String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID); //从TaskDetailFragment中传递过来的Intent中取Task id,key用的就是这个AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID

        setToolbarTitle(taskId); //设置toolBar的标题

        if (addEditTaskFragment == null) { //如果没有拿到fragment
            addEditTaskFragment = AddEditTaskFragment.newInstance(); //这边当然要创建一个fragment了

            if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) { //如果Intent中包含ARGUMENT_EDIT_TASK_ID这个key
                Bundle bundle = new Bundle(); //new一个Bundle
                bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId); //Bundle对象中放入taskId
                addEditTaskFragment.setArguments(bundle); //然后把这个Bundle放入到fragment里
            }

            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), //将fragment依附到activity中
                    addEditTaskFragment, R.id.contentFrame);  //需要传入fragmentManager、fragment、还有布局中的占位id
        }

        boolean shouldLoadDataFromRepo = true; //标志位,是否从仓库中读取数据

        // Prevent the presenter from loading data from the repository if this is a config change.
        if (savedInstanceState != null) { //若Bundle不为null,那就是说,是从上次意外的Activity对象中恢复
            // Data might not have loaded when the config change happen, so we saved the state.
            shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY); //根据key,从Bundle中取标志位,并赋值给shouldLoadDataFromRepo
        }

        // Create the presenter 创建presenter
        mAddEditTaskPresenter = new AddEditTaskPresenter(
                taskId, //传入taskId对象,其实是一个String对象
                Injection.provideTasksRepository(getApplicationContext()), //传入TasksRepository对象
                addEditTaskFragment, //传入fragment对象
                shouldLoadDataFromRepo); //传入一个标志位,是否从仓库中读取数据
    }

    /**
     * 设置ToolbarTitle的标题
     * @param taskId
     */
    private void setToolbarTitle(@Nullable String taskId) {
        if(taskId == null) { //先判断,如果taskId为null
            mActionBar.setTitle(R.string.add_task); //标题就写为New TO-DO
        } else {
            mActionBar.setTitle(R.string.edit_task); //如果taskId不是null,那就是Edit TO-DO
        }
    }

    /**
     * 如果Activity对象被意外回收,无论是进程被撸掉,还是Activity Task栈被撸掉,都会调用该方法
     * @param outState
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Save the state so that next time we know if we need to refresh data.
        outState.putBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY, mAddEditTaskPresenter.isDataMissing());//把想保留的对象放进Bundle中
        super.onSaveInstanceState(outState);
    }

    /**
     * 又看见这个方法了,鬼知道怎么回事
     * @return
     */
    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }

    /**
     * 单元测试用的
     * @return
     */
    @VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }

}

 

3、Presenter的实现类,欧耶

/**
 * Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
 * the UI as required.
 * AddEditTask页用的Presenter
 */
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
        TasksDataSource.GetTaskCallback {

    @NonNull
    private final TasksDataSource mTasksRepository; //Task仓库对象,TasksDataSource

    @NonNull
    private final AddEditTaskContract.View mAddTaskView; //AddEditTaskContract.View对象

    @Nullable
    private String mTaskId; //TaskId对象

    private boolean mIsDataMissing; //是否数据丢失的标志位

    /**
     * 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) {
        mTaskId = taskId; //传进来的Task id
        mTasksRepository = checkNotNull(tasksRepository); //传进来的Tasks仓库对象
        mAddTaskView = checkNotNull(addTaskView); //传进来的fragment对象,因为fragment对象实现了AddEditTaskContract.View
        mIsDataMissing = shouldLoadDataFromRepo; //从仓库加载数据时,如果数据错过了

        mAddTaskView.setPresenter(this); //调用fragment中的setPresenter方法,把当前的Presenter对象传过去。setPresenter是AddEditTaskContract.View的接口方法,fragment实现了
    }

    /**
     * fragment创建后,会在它的onResume中调用start()方法
     *
     */
    @Override
    public void start() {
        if (!isNewTask() && mIsDataMissing) { //如果不是新的Task、又有数据丢失了
            populateTask();
        }
    }

    /**
     * 保存Task
     * @param title 标题
     * @param description 内容
     */
    @Override
    public void saveTask(String title, String description) {
        if (isNewTask()) { //如果新的Task
            createTask(title, description); //创建Task
        } else {
            updateTask(title, description); //不是新的Task,那就是更新Task
        }
    }

    /**
     * 这个方法是,从仓库获取Task吗?populate这个单词要怎么解释,确实是,如果不是新的Task,那就从仓库中拿Task
     */
    @Override
    public void populateTask() {
        if (isNewTask()) { //如果是新的Task
            throw new RuntimeException("populateTask() was called but task is new."); //抛出RuntimeException异常,提示为"populateTask() was called but task is new."
        }
        mTasksRepository.getTask(mTaskId, this); //旧的Task,通过TaskId,调用Task仓库对象的getTask
        //getTask方法需要一个GetTaskCallback对象,我们传this进去就可以了,因为 AddEditTaskPresenter 实现了GetTaskCallback接口
    }

    /**
     * GetTaskCallback下的接口方法,这个方法在mTasksRepository中的getTask方法中会被调用哦
     * @param task 要传入的Task对象
     */
    @Override
    public void onTaskLoaded(Task task) {
        // The view may not be able to handle UI updates anymore
        if (mAddTaskView.isActive()) { //如果fragment已经依附到Activity中
            mAddTaskView.setTitle(task.getTitle()); //调用其设置标题
            mAddTaskView.setDescription(task.getDescription()); //调用其设置详细描述
        }
        mIsDataMissing = false; //并且把标志
    }

    /**
     *  当数据,即Task没有获得到的时候
     */
    @Override
    public void onDataNotAvailable() {
        // The view may not be able to handle UI updates anymore
        if (mAddTaskView.isActive()) {  //首先fragment得依附到Activity上吧
            mAddTaskView.showEmptyTaskError(); //展示一个错误的Task Error
        }
    }

    /**
     *  判断数据是否丢失
     * @return 丢失状态标志位
     */
    @Override
    public boolean isDataMissing() {
        return mIsDataMissing;
    }

    /**
     * 判断是否为新的Task,如果mTaskId为null的话,则为新的Task
     * @return
     */
    private boolean isNewTask() {
        return mTaskId == null;
    }

    /**
     * 创建Task的方法
     * @param title 标题
     * @param description 详细内容
     */
    private void createTask(String title, String description) {
        Task newTask = new Task(title, description); //先new个Task对象,传入了标题和描述
        if (newTask.isEmpty()) { //判断Task是否为空
            mAddTaskView.showEmptyTaskError(); //如果为空,展示空Task的Error提示
        } else {  //不为空
            mTasksRepository.saveTask(newTask); //放入Task仓库中,存储Task
            mAddTaskView.showTasksList(); //马上finish掉当前的edit页,展示TasksList页
        }
    }

    /**
     * 更新Task的方法
     * @param title 要更新的标题
     * @param description 要更新的详细描述
     */
    private void updateTask(String title, String description) {
        if (isNewTask()) { //如果是新的Task
            throw new RuntimeException("updateTask() was called but task is new."); //直接抛出RuntimeException异常,给的描述:"updateTask() was called but task is new."
        }
        mTasksRepository.saveTask(new Task(title, description, mTaskId)); //去仓库保存Task
        mAddTaskView.showTasksList(); // After an edit, go back to the list. //更新完了之后,back返回到Task列表页
    }
}

 

4、Fragment,其实也是interface中的View的实现类,欧耶

/**
 * Main UI for the add task screen. Users can enter a task title and description.
 */
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {

    public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID"; //嘿嘿,放入Bundle时,用的key

    private AddEditTaskContract.Presenter mPresenter;  //嘿嘿,Presenter

    private TextView mTitle; //一个View的引用,标题

    private TextView mDescription; //一个View的引用,详细内容

    /**
     * 创建fragment对象的静态方法,倒是挺轻松的哈
     * @return
     */
    public static AddEditTaskFragment newInstance() {
        return new AddEditTaskFragment(); //new一个构造方法嘛
    }

    /**
     * 默认的构造方法,必须得有,没招
     */
    public AddEditTaskFragment() {
        // Required empty public constructor
    }

    /**
     * 生命周期方法
     */
    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start(); //在这里调用Presenter的start()方法
        //有没有担心mPresenter没有初始化完成,这个完全不用担心,因为是在AddEditTaskActivity的onCreate
        //那就更没有必要担心,Activity的onResume()方法没有调用完的话,fragment的onResume怎么会执行呢,在速度上,肯定presenter不会是null嘛
        //我这么理解比较风骚
    }

    /**
     * 嘿嘿,就是这个方法,在AddEditTaskPresenter的构造方法里调用,而AddEditTaskPresenter是在AddEditTaskActivity创建的哦
     * @param presenter
     */
    @Override
    public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

    /**
     * fragment的生命周期方法,在activity的onCreate()调用完后,会调用这个,它是在onCreateView方法后面调用的
     * @param savedInstanceState
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState); //老规矩,基类到还是必须调用的

        FloatingActionButton fab =
                (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done); //从依附的Activity中获取View,这是一个FAB的控件
        fab.setImageResource(R.drawable.ic_done); //给fab设置图标
        fab.setOnClickListener(new View.OnClickListener() { //给fab设置点击事件监听器对象
            @Override
            public void onClick(View v) {
                mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString()); //调用mPresenter的saveTask方法,传入title和内容
            }
        });
    }

    /**
     * 生命周期方法
     * @param inflater LayoutInflater对象,用于解析布局xml文件
     * @param container ViewGroup对象,View的容器对象
     * @param savedInstanceState 一个Bundle对象
     * @return
     */
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.addtask_frag, container, false); //初始化root view
        mTitle = (TextView) root.findViewById(R.id.add_task_title); //获取view title
        mDescription = (TextView) root.findViewById(R.id.add_task_description); //获取view 详细description
        setHasOptionsMenu(true); //设置展示optionsMenu
        return root; //返回初始化后的View
    }

    /**
     *  展示一个空Task的Error提示,展示一个Snackbar
     */
    @Override
    public void showEmptyTaskError() {
        Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
    }

    /**
     *  展示TasksList,怎么展示呢,把当前的EditTaskActivity,finish掉
     */
    @Override
    public void showTasksList() {
        getActivity().setResult(Activity.RESULT_OK); //首先设置结果码
        getActivity().finish(); //finish掉整个Activity
    }

    /**
     * 设置标题
     * @param title
     */
    @Override
    public void setTitle(String title) {
        mTitle.setText(title);
    }

    /**
     *  设置详细内容
     * @param description
     */
    @Override
    public void setDescription(String description) {
        mDescription.setText(description);
    }

    /**
     * 判断一下fragment是否已经依附到Activity上
     * @return
     */
    @Override
    public boolean isActive() {
        return isAdded();
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值