从google的todo学习MVP

todo-mvp
点击阅读原文

背景知识

什么是todo?

todo就是简单的记事app
这里写图片描述

什么是MVP?

网上有很多概念,我也看了很多,一直只有一个模糊的印象。最近通过分析和模仿todo-mvp写了个demo有了完全不同的理解。
Model:提供数据,从本地或从云端
View:View即视图
Presenter:负责逻辑的处理,从Model中取数据,然后处理,传给View层,View将其展示

开始分析todo-MVP

项目结构

项目结构
data包为整个app提供数据包括local和remote,local包下就是封装了对数据库的操作,这里我们重点关注的是TasksRepository,它封装了数据的获取,从本地或者云端。
tasks包和taskdetail以及addedittask打开后结构是一样的,都包含一个activity一个fragment一个contract一个presener,所以我们只以task包为例分析,既最上面的那张图。
先来看看各个不同的包和不同的接口是怎么配合的。
Repository为M,Presenter为P,View为V
MVP
在这之前我们先来说说接口BaseView和BasePresenter,BaseView中只有一个setPresenter(),BasePresenter只有一个star()
然后看看TasksContract`

public interface TasksContract {

    interface View extends BaseView<Presenter> {

        ....

        void showTasks(List<Task> tasks);

        void showLoadingTasksError();

        void showNoTasks();
        ....
    }

    interface Presenter extends BasePresenter {
        ....

        void loadTasks(boolean forceUpdate);

        void openTaskDetails(@NonNull Task requestedTask);

        void clearCompletedTasks();
        ....
    }
}

contract中有两个接口分别实现BaseView和BasePresenter,Presenter中在加入在Tasks中可能发生的操作,BaseView中加入View的响应操作。

View

接下来看看Fragment即View

public class TasksFragment extends Fragment implements TasksContract.View {
    ....
    private TasksContract.Presenter mPresenter;
    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }
    @Override
    public void setPresenter(@NonNull TasksContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }
    @Override
    public void showTasks(List<Task> tasks) {
        mListAdapter.replaceData(tasks);

        mTasksView.setVisibility(View.VISIBLE);
        mNoTasksView.setVisibility(View.GONE);
    }
    @Override
    public void showNoTasks() {
        showNoTasksViews(
                getResources().getString(R.string.no_tasks_all),
                R.drawable.ic_assignment_turned_in_24dp,
                false
        );
    }

    private void showNoTasksViews(String mainText, int iconRes, boolean showAddView) {
        mTasksView.setVisibility(View.GONE);
        mNoTasksView.setVisibility(View.VISIBLE);

        mNoTaskMainView.setText(mainText);
        mNoTaskIcon.setImageDrawable(getResources().getDrawable(iconRes));
        mNoTaskAddView.setVisibility(showAddView ? View.VISIBLE : View.GONE);
    }
    ....
}

注意在OnResume中调用了star(),一般就是从Repository中获取数据,View层根据接口返回的数据进行UI操作,同时根据监听事件调用Presenter相应的方法(presenter中有对View接口的引用,之后会回调)

Presenter

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);
    }

    @Override
    public void start() {
        loadTasks(false);
    }

    @Override
    public void loadTasks(boolean forceUpdate) {
        // Simplification for sample: a network reload will be forced on first load.
        loadTasks(forceUpdate || mFirstLoad, true);
        mFirstLoad = false;
    }

    private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
        if (showLoadingUI) {
            mTasksView.setLoadingIndicator(true);
        }
        if (forceUpdate) {
            mTasksRepository.refreshTasks();
        }

        // The network request might be handled in a different thread so make sure Espresso knows
        // that the app is busy until the response is handled.
        EspressoIdlingResource.increment(); // App is busy until further notice

        mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                List<Task> tasksToShow = new ArrayList<Task>();

                // This callback may be called twice, once for the cache and once for loading
                // the data from the server API, so we check before decrementing, otherwise
                // it throws "Counter has been corrupted!" exception.
                if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
                    EspressoIdlingResource.decrement(); // Set app as idle.
                }

                // We filter the tasks based on the requestType
                for (Task task : tasks) {
                    switch (mCurrentFiltering) {
                        case ALL_TASKS:
                            tasksToShow.add(task);
                            break;
                        case ACTIVE_TASKS:
                            if (task.isActive()) {
                                tasksToShow.add(task);
                            }
                            break;
                        case COMPLETED_TASKS:
                            if (task.isCompleted()) {
                                tasksToShow.add(task);
                            }
                            break;
                        default:
                            tasksToShow.add(task);
                            break;
                    }
                }
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                if (showLoadingUI) {
                    mTasksView.setLoadingIndicator(false);
                }

                processTasks(tasksToShow);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                mTasksView.showLoadingTasksError();
            }
        });
    }
    @Override
    public void openTaskDetails(@NonNull Task requestedTask) {
        checkNotNull(requestedTask, "requestedTask cannot be null!");
        mTasksView.showTaskDetailsUi(requestedTask.getId());
    }

    @Override
    public void completeTask(@NonNull Task completedTask) {
        checkNotNull(completedTask, "completedTask cannot be null!");
        mTasksRepository.completeTask(completedTask);
        mTasksView.showTaskMarkedComplete();
        loadTasks(false, false);
    }
    ....
}

在TasksPresenter中在实例化的时候持有了对Repository和TaskContract.View的引用,并且调用setPresenter()给TasksContract.View设置Presenter。当View层调用Presenter的方法时他进行相应的对Repository的操作,完成后根据情况回调TaskContract.View的相应方法。

Model

首先我们先想想需要对数据做什么操作,就todo而言,一般就是需要获取Tasks,删除Task等等,所以我们先抽象出一个接口集

public interface TasksDataSource {
    ....
    interface LoadTasksCallback {
        void onTasksLoaded(List<Task> tasks);
        void onDataNotAvailable();
    }
    interface GetTaskCallback {
        void onTaskLoaded(Task task);
        void onDataNotAvailable();
    }
    void getTasks(@NonNull LoadTasksCallback callback);
    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
    void saveTask(@NonNull Task task);
    void deleteAllTasks();
    void deleteTask(@NonNull String taskId);
    ....
}

内部的两个接口是用来将数据回调到外部的,现在来看看repository的实现

public class TasksRepository implements TasksDataSource {
    ....
    private static TasksRepository INSTANCE = null;
    private final TasksDataSource mTasksRemoteDataSource;
    private final TasksDataSource mTasksLocalDataSource;
    private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                            @NonNull TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
    }
    public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
                                              TasksDataSource tasksLocalDataSource) {
        if (INSTANCE == null) {
            INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
        }
        return INSTANCE;
    }
    @Override
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        checkNotNull(callback);
        if (mCachedTasks != null && !mCacheIsDirty) {
            callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            return;
        }
        if (mCacheIsDirty) {
            getTasksFromRemoteDataSource(callback);
        } else {
            mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                @Override
                public void onTasksLoaded(List<Task> tasks) {
                    refreshCache(tasks);
                    callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                }

                @Override
                public void onDataNotAvailable() {
                    getTasksFromRemoteDataSource(callback);
                }
            });
        }
    }
    @Override
    public void saveTask(@NonNull Task task) {
        checkNotNull(task);
        mTasksRemoteDataSource.saveTask(task);
        mTasksLocalDataSource.saveTask(task);
        if (mCachedTasks == null) {
            mCachedTasks = new LinkedHashMap<>();
        }
        mCachedTasks.put(task.getId(), task);
    }
    ....
}

从以上可知Repository中,它的作用是给Presenter提供数据,而它来处理从缓存还是从本地数据库或者从网络请求的逻辑操作,它持有一个TasksLocalDataSource和TasksRemoteDataSource的引用,而真正对数据库和网络数据操作的的就是TasksLocalDataSource和TasksRemoteDataSource,他们同样实现了接口TasksDataSource,具体代码可以打开链接查看。

最后我们再来理一下各个类间的关系,TasksPresenter持有TasksContract.View和Repository的引用,实现了TasksContract.View的Fragment持有Presenter的引用,Repository持有TasksLocalDataSoure和TasksRemoteDataSource的引用。

最后我们需要一个Activity来把这所有东西调动以来

public class TasksActivity extends AppCompatActivity {
    private TasksPresenter mTasksPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasks_act);
        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);
    }
}

现在整个MVP就完整了。第一次认真写博客,请多包涵。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值