MVP 谷歌demo小解(三)

来源:互联网 发布:同花顺软件k线图 编辑:程序博客网 时间:2024/05/29 11:01

官方项目地址

https://github.com/googlesamples/android-architecture

项目结构图

这里写图片描述
Project视图下

  • src/main-项目路径
  • src/androidTest-UI层测试
  • src/androidTestMock-UI层测试mock数据支持
  • src/test-业务层单元测试
  • src/mock-业务层单元测试mock数据支持

项目包是按功能来划分的,每个包内又有XActivity、XContract、XFragment、XPresenter。其中XContract有View实体和Presenter实体所用到的接口;XFragment是View的实体;XPresenter是Presenter的实体;XActivity做些关联及其他基础操作。

app的build.gradle

因为当前没有对测试做深入探究,所以测试相关,要等后面作深入研究后再做解析

dependencies {    // App's dependencies, including test    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"    compile "com.android.support:cardview-v7:$rootProject.supportLibraryVersion"    compile "com.android.support:design:$rootProject.supportLibraryVersion"    compile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"    compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"    compile "com.android.support.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"    compile "com.google.guava:guava:$rootProject.guavaVersion"    // Dependencies for local unit tests    testCompile "junit:junit:$rootProject.ext.junitVersion"    testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"    testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion"    // Android Testing Support Library's runner and rules    androidTestCompile "com.android.support.test:runner:$rootProject.ext.runnerVersion"    androidTestCompile "com.android.support.test:rules:$rootProject.ext.runnerVersion"    // Dependencies for Android unit tests    androidTestCompile "junit:junit:$rootProject.ext.junitVersion"    androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"    androidTestCompile 'com.google.dexmaker:dexmaker:1.2'    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'    // Espresso UI Testing    androidTestCompile "com.android.support.test.espresso:espresso-core:$rootProject.espressoVersion"    androidTestCompile ("com.android.support.test.espresso:espresso-contrib:$rootProject.espressoVersion")    androidTestCompile "com.android.support.test.espresso:espresso-intents:$rootProject.espressoVersion"    // Resolve conflicts between main and test APK:    androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion"    androidTestCompile "com.android.support:support-v4:$rootProject.supportLibraryVersion"    androidTestCompile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"}

工程文件

BaseView.java

public interface BaseView<T> {    // view关联presenter    void setPresenter(T presenter);}

BasePresenter.java

public interface BasePresenter {    //第一次加载数据    void start();}

TasksActivity.java

启动的第一个activity,其实跟MVP相关的就下面代码,其他都是配置菜单栏之类的。当前项目的实现都是,activity中使用FrameLayout控件来放一个fragment作为展示的界面;activity创建presenter并将其与fragment关联。

TasksFragment tasksFragment =(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);if (tasksFragment == null) {    // 实例化tasksFragment    tasksFragment = TasksFragment.newInstance();    // 将tasksFragment添加到FrameLayout    ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), tasksFragment, R.id.contentFrame);}// 实例化presentermTasksPresenter = new TasksPresenter(Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

我们看到先new的TasksView然后是new TasksPresenter,会不会出现TasksView执行onResume时,TasksPresenter还没有赋值呢?做了个测试,验证了下是赋过值的。相关方法执行顺序

Created with Raphaël 2.1.0开始TasksView的构造方法TasksPresenter的构造方法TasksFragment.onCreateTasksFragment.onCreateViewTasksFragment.onResume结束

TasksContract.java(View层与Presenter层接口)

当前功能的view层和presenter层的接口,TasksFragment.java和TasksPresenter.java都要实现相关的方法。罗列出部分

public interface TasksContract {    interface View extends BaseView<Presenter> {        //setPresenter()在BaseView中        //当前界面显示tasks        void showTasks(List<Task> tasks);        //跳转到添加task页        void showAddTask();            }    interface Presenter extends BasePresenter {        //start()在BasePresenter中        //Fragment的onActivityResult调用        void result(int requestCode, int resultCode);        //加载tasks        void loadTasks(boolean forceUpdate);        //item的点击会调用用的三个方法        void openTaskDetails(@NonNull Task requestedTask);        void completeTask(@NonNull Task completedTask);        void activateTask(@NonNull Task activeTask);            }}

FakeTasksRemoteDataSource.java(model层)

TasksActivity中

//TasksPresenter第一个参数,就是model层的实例mTasksPresenter = new TasksPresenter(Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

TasksRepository单例方式实例化对象

public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,TasksDataSource tasksLocalDataSource) {    if (INSTANCE == null) {            INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);        }    return INSTANCE;}

接口对象中,会用到的几个方法

public interface TasksDataSource {    interface LoadTasksCallback {        void onTasksLoaded(List<Task> tasks);        void onDataNotAvailable();    }    void getTasks(@NonNull LoadTasksCallback callback);}

流程

应用启动后,TaskActivity初始化做的事情

Created with Raphaël 2.1.0TasksPresenterTasksPresenterTasksFragmentTasksFragmentTasksPresenter构造函数调用TasksFragment的setPresenter将TasksPresenter实例传给TasksFragment供其使用TasksFragment的onResume调用TasksPresenter的start( )TasksPresenter初始化相关数据,根据结果调用TasksFragment的相关方法(详见流程图及相关类)调用TasksFragment相关方法界面的相关操作。如点击,调用TasksPresenter相关方法

环节有所简化(显示/隐藏加载进度框;Model层的强制刷新等),不然流程图占屏幕太大了

Created with Raphaël 2.1.0开始Presenter:View的onResume调用Presenter的startPresenter:loadTasksModel:获取task数据success?View:showTasks结束View:showLoadingTasksErroryesno

如何抽取接口方法?

Model层

与数据库、网络请求有关的操作,如getTasks

void getTasks(@NonNull LoadTasksCallback callback);

Presenter层

界面的初始化界面数据、响应View层的事件

//onActivityResult的事件调用void result(int requestCode, int resultCode);//初始化void loadTasks(boolean forceUpdate);//点击添加新taskvoid addNewTask();//点击task查看详情void openTaskDetails(@NonNull Task requestedTask);

View层

当前项目中,几个比较典型的

这层抽取的细节比较多,所以有些难取舍。
1. 让Presenter能够关联到View,也是基类中的方法

public void setPresenter(@NonNull TasksContract.Presenter presenter);

2. 初始化数据成功后,刷新显示界面;初始化数据成功后,但是tasks长度为0则showNoTasks

public void showTasks(List<Task> tasks);public void showNoTasks();

3. 显示/隐藏正在加载对话框

public void setLoadingIndicator(boolean active);

4. 点击跳转到其他界面,在View调用提供的方法

mNoTaskAddView.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        showAddTask();    }});@Overridepublic void showAddTask() {   Intent intent = new Intent(getContext(), AddEditTaskActivity.class);   startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);}

点击跳转到其他界面,在Presenter调用View提供的方法

//TasksPresenter代码@Overridepublic void openTaskDetails(@NonNull Task requestedTask) {    checkNotNull(requestedTask, "requestedTask cannot be null!");    mTasksView.showTaskDetailsUi(requestedTask.getId());}//TasksFragment代码TaskItemListener mItemListener = new TaskItemListener() {        @Override        public void onTaskClick(Task clickedTask) {            mPresenter.openTaskDetails(clickedTask);        }};@Overridepublic void showTaskDetailsUi(String taskId) {    // in it's own Activity, since it makes more sense that way and it gives us the flexibility    // to show some Intent stubbing.    Intent intent = new Intent(getContext(), TaskDetailActivity.class);    intent.putExtra(TaskDetailActivity.EXTRA_TASK_ID, taskId);    startActivity(intent);}

两种方式均可,但个人更偏向于第一种。点击事件当前View处理即可
5. onActivityResult方法

@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {    mPresenter.result(requestCode, resultCode);}

6. Toast提示信息,也要抽取成方法。方便View界面调用

private void showMessage(String message);

7. ListView或Adapter中如果需要使用Presenter层实例方法时,以接口回调形式实现

/** * Listener for clicks on tasks in the ListView. */TaskItemListener mItemListener = new TaskItemListener() {    @Override    public void onTaskClick(Task clickedTask) {        mPresenter.openTaskDetails(clickedTask);    }    @Override    public void onCompleteTaskClick(Task completedTask) {        mPresenter.completeTask(completedTask);    }    @Override    public void onActivateTaskClick(Task activatedTask) {        mPresenter.activateTask(activatedTask);    }};private static class TasksAdapter extends BaseAdapter {   private List<Task> mTasks;   private TaskItemListener mItemListener;   public TasksAdapter(List<Task> tasks, TaskItemListener itemListener) {       setList(tasks);       mItemListener = itemListener;   }}

其他的都类比着来就可以了。

其他项目中我抽取的

1. 设置title栏的值;设置title栏按钮的文字

@Overridepublic void setUiTitle(int titleId){    mTvTitle.setText(titleId);}@Overridepublic void setRightBtText(int rightBtTextId){    mTvRight.setText(rightBtTextId);}

2. 添加点击事件当Presenter实例化的时候调用;处理点击事件

//添加点击事件的接口public void initOnClickListener();//View中处理点击事件public void onClick(View v) {    switch (v.getId()) {        case R.id.tv_right://点击添加分类            mGoodsKindPresenter.addKind();            break;    }}

3. 添加文本监听,当Presenter实例化的时候调用initTextChangedListener,添加文本监听事件

/** * 添加文本改变的监听 */@Overridepublic void initTextChangedListener(); 

4. 获取activity的context参数

public void Context getContext();

5. 界面弹出框及内容的点击事件监听

/** * 加载类别弹出框ui * @param parentList * @param childrenList */void showDialogSelectedKinds(List<KindDomain> parentList, List<KindDomain> childrenList);/** * 选取父分类 * @param domain */void dialogOnItemClick(KindDomain domain);

我就罗列这么多吧,其他的自己琢磨吧!
View和Model之间的耦合度降低,使其更关注自身业务逻辑,结构清晰,维护方便。这样也便于单元测试,代码框架更适用于快速迭代开发。但也有缺点就是类很多

项目效果图

这里写图片描述

0 0
原创粉丝点击