MVP 谷歌demo小解(三)

本文详细介绍了MVP架构的实现方式,包括项目结构、关键组件的角色和职责,以及它们之间的交互流程。通过具体示例展示了如何设计和组织代码,以便更好地进行单元测试和维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官方项目地址

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);
}
// 实例化presenter
mTasksPresenter = new TasksPresenter(Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

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

Created with Raphaël 2.1.0 开始 TasksView的构造方法 TasksPresenter的构造方法 TasksFragment.onCreate TasksFragment.onCreateView TasksFragment.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.0 TasksPresenter TasksPresenter TasksFragment TasksFragment TasksPresenter构造函数调用TasksFragment的setPresenter 将TasksPresenter实例传给 TasksFragment供其使用 TasksFragment的onResume调用TasksPresenter的start( ) TasksPresenter初始化相关数 据,根据结果调用TasksFragment 的相关方法(详见流程图及相关类) 调用TasksFragment相关方法 界面的相关操作。如点击,调用TasksPresenter相关方法

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

Created with Raphaël 2.1.0 开始 Presenter:View的onResume调用Presenter的start Presenter:loadTasks Model:获取task数据 success? View:showTasks 结束 View:showLoadingTasksError yes no

如何抽取接口方法?

Model层

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

void getTasks(@NonNull LoadTasksCallback callback);

Presenter层

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

//onActivityResult的事件调用
void result(int requestCode, int resultCode);
//初始化
void loadTasks(boolean forceUpdate);
//点击添加新task
void 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();
    }
});

@Override
public void showAddTask() {
   Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
   startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);
}

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

//TasksPresenter代码
@Override
public 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);
        }
};

@Override
public 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方法

@Override
public 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栏按钮的文字

@Override
public void setUiTitle(int titleId){
    mTvTitle.setText(titleId);
}

@Override
public 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,添加文本监听事件

/**
 * 添加文本改变的监听
 */
@Override
public 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之间的耦合度降低,使其更关注自身业务逻辑,结构清晰,维护方便。这样也便于单元测试,代码框架更适用于快速迭代开发。但也有缺点就是类很多

项目效果图

这里写图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值