前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Google Android MVP示例解读

Google Android MVP示例解读

作者头像
luciozhang
发布2023-04-22 16:23:16
2880
发布2023-04-22 16:23:16
举报
文章被收录于专栏:前端lucio前端lucio

Google MVP示例

Google MVP架构示例:https://github.com/googlesamples/android-architecture

Android为开发者对如何设计一个app的代码结构提供了很高的灵活性,但这也同时可能带来代码结构混乱、可读性差等问题。Google MVP示例为app的代码结构设计提供了一个参考已解决灵活性带来的问题,就像文档中说的,具体设计还需要视app的具体情况调整。

官网的示例中给出了基于MVP模式使用不同框架和工具的实现,我们从最基本的MVP架构开始了解。

MVP模式

Model‑View‑Presenter

这里写图片描述
这里写图片描述
  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。
  3. View 不处理业务逻辑,称为"被动视图"(Passive View),即没有任何主动性。
  4. 所有的交互都发生在Presenter。
  5. Model不是简单的定义实体,还需要完成数据获取、数据存储、数据变换的任务。

Google MVP示例解读

取TO-DO-MVP的详情模块为例。

google mvp
google mvp

我们需要关注MVP各个部分具体实现的类,以及V-P和P-M之间通信的方式。

BasePresenter和BaseView两个基础类

代码语言:javascript
复制
public interface BaseView<T> {
    // 用于绑定Presenter,在BasePresenter实现类的构造器中,传入BaseView,再调用其setPresenter方法
    void setPresenter(T presenter);
}
代码语言:javascript
复制
public interface BasePresenter {
    // 用于开始获取数据并调用View的方法更新UI,一般在Fragment的onResume方法中调用。
    void start();
}

TaskDetailContract 契约接口

用于统一管理Presenter和View接口,指定Presenter实现的功能和View实现的UI操作。

代码语言:javascript
复制
/**
 * This specifies the contract between the view and the presenter.
 */
public interface TaskDetailContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showMissingTask();

        void hideTitle();

        void showTitle(String title);

        void hideDescription();

        void showDescription(String description);

        void showCompletionStatus(boolean complete);

        void showEditTask(String taskId);

        void showTaskDeleted();

        void showTaskMarkedComplete();

        void showTaskMarkedActive();

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

        void editTask();

        void deleteTask();

        void completeTask();

        void activateTask();
    }
}

TaskDetailFragment ——View

实现TaskDetailContract.View接口。

代码语言:javascript
复制
@Override
public void onResume() {
        super.onResume();
        //通知mPresenter获取数据,mPresenter.start()获取到数据后,再通知View更改数据
        mPresenter.start();
}
代码语言:javascript
复制
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
        //为View绑定Presenter
        mPresenter = checkNotNull(presenter);
}
代码语言:javascript
复制
@Override
public void showCompletionStatus(final boolean complete) {
        Preconditions.checkNotNull(mDetailCompleteStatus);

        //更新UI
        mDetailCompleteStatus.setChecked(complete);
        mDetailCompleteStatus.setOnCheckedChangeListener(
                new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        if (isChecked) {
                            //监听到事件时,通知Presenter工作。
                            mPresenter.completeTask();
                        } else {
                            mPresenter.activateTask();
                        }
                    }
                });
}

TaskDetailPresenter——Presenter

实现TaskDetailContract.Presenter。

代码语言:javascript
复制
public TaskDetailPresenter(@Nullable String taskId,
                           @NonNull TasksRepository tasksRepository,
                           @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        //为Presenter绑定View
          mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
        //为View绑定Presenter
        mTaskDetailView.setPresenter(this);
}
代码语言:javascript
复制
@Override
public void start() {
        //初始更新界面
        openTask();
 }

private void openTask() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }

        mTaskDetailView.setLoadingIndicator(true);
        //Model获取数据
        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // The view may not be able to handle UI updates anymore
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.setLoadingIndicator(false);
                if (null == task) {
                    //通知View更新界面
                    mTaskDetailView.showMissingTask();
                } else {
                    showTask(task);
                }
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.showMissingTask();
            }
        });
}
代码语言:javascript
复制
@Override
public void completeTask() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }
          //通知Model变换数据
        mTasksRepository.completeTask(mTaskId);
          //通知View更新界面
        mTaskDetailView.showTaskMarkedComplete();
}

TasksLocalDataSource和TasksRemoteDataSource单例——Model

实现TasksDataSource。

TasksDataSource

代码语言:javascript
复制
public interface TasksDataSource {
    //回调接口可被Presenter实现,可在获取数据成功/失败后,让Presenter做出处理,如通知View更新等。
    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 completeTask(@NonNull Task task);

    void completeTask(@NonNull String taskId);

    void activateTask(@NonNull Task task);

    void activateTask(@NonNull String taskId);

    void clearCompletedTasks();

    void refreshTasks();

    void deleteAllTasks();

    void deleteTask(@NonNull String taskId);
}

TasksLocalDataSource

代码语言:javascript
复制
/**
 * Concrete implementation of a data source as a db.
 */
public class TasksLocalDataSource implements TasksDataSource {

    private static TasksLocalDataSource INSTANCE;

    private TasksDbHelper mDbHelper;

    // Prevent direct instantiation.
    private TasksLocalDataSource(@NonNull Context context) {
        checkNotNull(context);
        mDbHelper = new TasksDbHelper(context);
    }

    public static TasksLocalDataSource getInstance(@NonNull Context context) {
        if (INSTANCE == null) {
            INSTANCE = new TasksLocalDataSource(context);
        }
        return INSTANCE;
    }

    /**
     * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesn't exist
     * or the table is empty.
     */
    @Override
    public void getTasks(@NonNull LoadTasksCallback callback) {
        List<Task> tasks = new ArrayList<Task>();
        SQLiteDatabase db = mDbHelper.getReadableDatabase();

        String[] projection = {
                TaskEntry.COLUMN_NAME_ENTRY_ID,
                TaskEntry.COLUMN_NAME_TITLE,
                TaskEntry.COLUMN_NAME_DESCRIPTION,
                TaskEntry.COLUMN_NAME_COMPLETED
        };

        Cursor c = db.query(
                TaskEntry.TABLE_NAME, projection, null, null, null, null, null);

        if (c != null &amp;&amp; c.getCount() > 0) {
            while (c.moveToNext()) {
                String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID));
                String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE));
                String description =
                        c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION));
                boolean completed =
                        c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1;
                Task task = new Task(title, description, itemId, completed);
                tasks.add(task);
            }
        }
        if (c != null) {
            c.close();
        }

        db.close();

        if (tasks.isEmpty()) {
            // This will be called if the table is new or just empty.
            callback.onDataNotAvailable();
        } else {
            callback.onTasksLoaded(tasks);
        }

    }

    ... ...
}

TaskDetailActivity——全局控制

负责创建View和Presenter,并将二者联系起来。

代码语言:javascript
复制
/**
 * Displays task details screen.
 */
public class TaskDetailActivity extends AppCompatActivity {

    public static final String EXTRA_TASK_ID = "TASK_ID";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.taskdetail_act);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);
        ab.setDisplayShowHomeEnabled(true);

        // Get the requested task id
        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);

        TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                .findFragmentById(R.id.contentFrame);

        if (taskDetailFragment == null) {
            taskDetailFragment = TaskDetailFragment.newInstance(taskId);

            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
                    taskDetailFragment, R.id.contentFrame);
        }

        // Create the presenter
        new TaskDetailPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                taskDetailFragment);
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }
}

读过示例后可以看到,整个工程的脉络非常清晰,MVP各个部分分工明确,使用fragment作为View比Activity有更高的灵活性,工程各个功能按模块划分,非常符合“高内聚,低耦合”的要求。另一方面,MVP各个部分的区分,也方便了我们对数据获取、界面更新等各个部分的代码进行单元测试。

项目说明中有这么一句:

The focus of this project is on demonstrating how to structure your code, design your architecture, and the eventual impact of adopting these patterns on testing and maintaining your app.

项目关注的重点在于:代码结构、整体架构、可测试性、可维护性。

应对不同的app,我们需要做不同的调整,但遵循MVP模式进行设计,是一个非常好的开始。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Google MVP示例
  • MVP模式
  • Google MVP示例解读
    • BasePresenter和BaseView两个基础类
      • TaskDetailContract 契约接口
        • TaskDetailFragment ——View
          • TaskDetailPresenter——Presenter
            • TasksLocalDataSource和TasksRemoteDataSource单例——Model
              • TasksDataSource
              • TasksLocalDataSource
            • TaskDetailActivity——全局控制
            相关产品与服务
            数据保险箱
            数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档