Google MVP示例

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
我們需要關注MVP各個部分具體實現的類,以及V-P和P-M之間通信的方式。

BasePresenter和BaseView兩個基礎類

public interface BaseView<T> {
    // 用於綁定Presenter,在BasePresenter實現類的構造器中,傳入BaseView,再調用其setPresenter方法
    void setPresenter(T presenter);
}
public interface BasePresenter {
    // 用於開始獲取數據並調用View的方法更新UI,一般在Fragment的onResume方法中調用。
    void start();
}

TaskDetailContract 契約接口

用於統一管理Presenter和View接口,指定Presenter實現的功能和View實現的UI操作。

/**
 * 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接口。

@Override
public void onResume() {
        super.onResume();
        //通知mPresenter獲取數據,mPresenter.start()獲取到數據後,再通知View更改數據
        mPresenter.start();
}
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
        //爲View綁定Presenter
        mPresenter = checkNotNull(presenter);
}
@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。

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);
}
@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();
            }
        });
}
@Override
public void completeTask() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }
        //通知Model變換數據
        mTasksRepository.completeTask(mTaskId);
        //通知View更新界面
        mTaskDetailView.showTaskMarkedComplete();
}

TasksLocalDataSource和TasksRemoteDataSource單例——Model

實現TasksDataSource。

TasksDataSource:

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

/**
 * 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 && 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,並將二者聯繫起來。

/**
 * 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模式進行設計,是一個非常好的開始。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章