Привыкайте писать вместе! Это 13-й день моего участия в «Новом ежедневном плане Nuggets · Апрельское задание по обновлению», нажмите, чтобы просмотреть подробности мероприятия .
Эта статья была написана, когда lucio был стажером в 2017 году и занимался Android-разработкой, сейчас вроде бы есть еще какие-то отсылки.
Пример Google MVP
Пример архитектуры Google MVP: github.com/googlesample…
Android предоставляет разработчикам высокую степень гибкости в разработке структуры кода приложения, но это также может привести к таким проблемам, как загроможденная структура кода и плохая читабельность. Пример Google MVP дает ссылку на дизайн структуры кода приложения.Проблема гибкости решена.Как говорится в документации,конкретный дизайн необходимо корректировать в соответствии с конкретной ситуацией приложения.
Пример на официальном сайте показывает реализацию различных фреймворков и инструментов на основе паттерна MVP Начнем с самой базовой архитектуры MVP.
Шаблон лучшего игрока
Модель-Вид-Презентатор
- Связь между каждой частью двусторонняя.
- View и Model не контактируют, передаются через Presenter.
- View не обрабатывает бизнес-логику и называется «Passive View», то есть не имеет никакой инициативы.
- Все взаимодействия происходят в Presenter.
- Модель — это не просто определение сущностей, но она также должна выполнять задачи сбора, хранения и преобразования данных.
Пример интерпретации Google MVP
Возьмем в качестве примера модуль деталей TO-DO-MVP.
Нам нужно сосредоточиться на классах, которые реализует каждая часть MVP, и на том, как взаимодействуют VP и PM.
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.
/**
* 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 —— Просмотр
Реализуйте интерфейс 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 —— Ведущий
Реализовать 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 Singleton —— Модель
Реализовать 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);
}
复制代码
ЗадачиLocalDataSource
/**
* 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 понятно, использование фрагмента как View имеет большую гибкость, чем Activity, и каждая функция проекта разделена на модулей, что очень соответствует принципу «высокая сплоченность, низкая связанность». С другой стороны, различие каждой части MVP также облегчает модульное тестирование кода каждой части, например сбор данных и обновление интерфейса.
В описании проекта сказано следующее:
Основное внимание в этом проекте уделяется демонстрации того, как структурировать ваш код, проектировать архитектуру и возможное влияние принятия этих шаблонов на тестирование и поддержку вашего приложения.
Основное внимание в проекте уделяется: структуре кода, общей архитектуре, тестируемости и ремонтопригодности.
В ответ на разные приложения нам нужно вносить разные коррективы, но следование модели MVP для дизайна — очень хорошее начало.