从google的todo学习MVP

来源:互联网 发布:战龙三国坐骑数据 编辑:程序博客网 时间:2024/06/05 00:21

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就完整了。第一次认真写博客,请多包涵。