Make writing a habit together! This is the 13th day of my participation in the “Gold Digging Day New Plan · April More Text Challenge”. Click here for more details.

This article was written by Lucio when he was engaged in Android development during his internship in 2017. Now, it still has some reference significance.

Google MVP sample

Google MVP architecture example: github.com/googlesampl…

Android offers developers a great deal of flexibility in how to design the code structure of an app, but it can also cause problems such as confusing code structure and poor readability. The Google MVP example provides a reference for the design of the code structure of the app. The flexibility issue has been solved, and as the documentation states, the specific design needs to be adjusted for the specific situation of the app.

The examples on the website show implementations based on the MVP pattern using different frameworks and tools, starting with the most basic MVP architecture.

The MVP pattern

The Model ‑ View ‑ Presenter

  1. Communication between the parts is two-way.
  2. A View and Model are not connected. They are both delivered by Presenter.
  3. A View does not handle business logic and is called a “Passive View”, that is, it has no initiative.
  4. All interaction takes place in the Presenter.
  5. Model is not a simple definition of entities, but also needs to complete the tasks of data acquisition, data storage and data transformation.

Google MVP example interpretation

Take the details module of to-do-MVP as an example.

We need to focus on the classes that are specifically implemented in the various parts of the MVP and how v-P and P-M communicate.

BasePresenter and BaseView are the two base classes

public interface BaseView<T> {
    In the constructor of the BasePresenter implementation class, pass in BaseView and call its setPresenter method
    void setPresenter(T presenter);
}
Copy the code
public interface BasePresenter {
    // The method used to start retrieving data and call the View updates the UI, normally called in the Fragment's onResume method.
    void start(a);
}
Copy the code

TaskDetailContract Indicates the contract interface

Used for unified management of Presenter and View interface, specify Presenter implementation functions and View implementation UI operations.

/** * This specifies the contract between the view and the presenter. */
public interface TaskDetailContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showMissingTask(a);

        void hideTitle(a);

        void showTitle(String title);

        void hideDescription(a);

        void showDescription(String description);

        void showCompletionStatus(boolean complete);

        void showEditTask(String taskId);

        void showTaskDeleted(a);

        void showTaskMarkedComplete(a);

        void showTaskMarkedActive(a);

        boolean isActive(a);
    }

    interface Presenter extends BasePresenter {

        void editTask(a);

        void deleteTask(a);

        void completeTask(a);

        void activateTask(a); }}Copy the code

TaskDetailFragment ——View

Implement the TaskDetailContract.View interface.

@Override
public void onResume(a) {
        super.onResume();
        Mpresby.start () notifies the View to change the data
        mPresenter.start();
}
Copy the code
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
		// Bind Presenter to View
        mPresenter = checkNotNull(presenter);
}
Copy the code
@Override
public void showCompletionStatus(final boolean complete) {
        Preconditions.checkNotNull(mDetailCompleteStatus);

		/ / update the UI
        mDetailCompleteStatus.setChecked(complete);
        mDetailCompleteStatus.setOnCheckedChangeListener(
                new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        if (isChecked) {
                        	// When an event is detected, the Presenter is notified to work.
                            mPresenter.completeTask();
                        } else{ mPresenter.activateTask(); }}}); }Copy the code

TaskDetailPresenter——Presenter

Implement TaskDetailContract. Presenter.

public TaskDetailPresenter(@Nullable String taskId,
                           @NonNull TasksRepository tasksRepository,
                           @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        // Bind View for Presenter
  		mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
		// Bind Presenter to View
        mTaskDetailView.setPresenter(this);
}
Copy the code
@Override
public void start(a) {
		// Initial update interface
        openTask();
 }

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

        mTaskDetailView.setLoadingIndicator(true);
        //Model retrieves data
        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) {
                    // Notify the View to update the interface
                    mTaskDetailView.showMissingTask();
                } else{ showTask(task); }}@Override
            public void onDataNotAvailable(a) {
                // The view may not be able to handle UI updates anymore
                if(! mTaskDetailView.isActive()) {return; } mTaskDetailView.showMissingTask(); }}); }Copy the code
@Override
public void completeTask(a) {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }
  		// Tell Model to transform data
        mTasksRepository.completeTask(mTaskId);
  		// Notify the View to update the interface
        mTaskDetailView.showTaskMarkedComplete();
}
Copy the code

TasksLocalDataSource and TasksRemoteDataSource singleton — Model

Implement TasksDataSource.

TasksDataSource

public interface TasksDataSource {
	// The callback interface can be implemented by the Presenter, and the Presenter can do something after retrieving data successfully or failing, such as notifying the View of an update.
    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable(a);
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable(a);
    }
	// The method to obtain, store, and transform data
    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(a);

    void refreshTasks(a);

    void deleteAllTasks(a);

    void deleteTask(@NonNull String taskId);
}
Copy the code

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 = newTask(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); }}... . }Copy the code

TaskDetailActivity — Global control

Responsible for creating and connecting views and Presenters.

/** * 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(a) {
        onBackPressed();
        return true; }}Copy the code

After reading the example, we can see that the whole project has a very clear context. The division of labor of each part of MVP is clear. Using fragment as View has higher flexibility than Activity, and each function of the project is divided by module, which is in line with the requirements of “high cohesion and low coupling”. On the other hand, the division of each part of MVP makes it easy to unit test the code of various parts such as data acquisition and interface update.

The project description reads:

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.

The project focuses on code structure, overall architecture, testability, and maintainability.

We need to make different adjustments for different apps, but following the MVP model is a great place to start.