Коли я починав свій шлях Android-розробника, слова «Архітектура мобільного додатка» викликали в мене глибоке здивування, гугл і статті на Хабре вганяли ще більшу депресію - дивлюся в книгу, бачу фігу. Думаю, якщо ти читаєш цю статтю, то вже не раз вивчав цю картинку і намагався зрозуміти, що відбувається: Проблема розуміння архітектурного підходу в мобільній розробці, як на мене, криється в абстрактності самої архітектури. У кожного розробника своє бачення того, як правильно реалізувати той чи інший патерн. Більш-менш пристойні приклади реалізації MVP знайшлися в англомовному секторі інтернету, що не дивно. Коротенько розберемо, що є що, і перейдемо, наприклад. Model – рівень даних. Не люблю використовувати термін «бізнес логіка», тому у своїх додатках я називаю йогоRepository і він спілкується з базою даних та мережею. View – рівень відображення. Це буде Activity , Fragment або Custom View , якщо ви не любите танців з бубном та взаємодії з життєвим циклом. Нагадаю, що спочатку всі додатки Android підпорядковані структурі MVC , де Controller це Activity або Fragment . Presenter- Прошарок між View і Model. View передає йому події, що відбуваються, презентер обробляє їх, при необхідності звертається до Model і повертає View дані на малювання. Стосовно Android і конкретного прикладу, виділю важливу частину - Contract. Це інтерфейс, який описує всі взаємодії між перерахованими компонентами. Резюмуючи теоретичну частину:
- View знає про Presenter;
- Presenter знає про View та Model (Repository);
- Model сама по собі;
- Контракт регулює взаємодії між ними.
MainContract
:
public interface MainContract {
interface View {
void showText();
}
interface Presenter {
void onButtonWasClicked();
void onDestroy();
}
interface Repository {
String loadMessage();
}
}
Поки що ми просто виділяємо 3 компоненти нашого майбутнього додатку і що вони робитимуть. Далі опишемо Repository:
public class MainRepository implements MainContract.Repository {
private static final String TAG = "MainRepository";
@Override
public String loadMessage() {
Log.d(TAG, "loadMessage()");
/** Здесь обращаемся к БД або сети.
* Я специально ничего не пишу, чтобы не загромождать пример
* DBHelper'ами и прочими не относяшимеся к теме об'єктами.
* Поэтому я буду возвращать строку Сосисочная =)
*/
return "Сосисочная у Лёхи»;
}
}
З ним все зрозуміло, просто завантаження – вивантаження даних. Далі на черзі Presenter:
public class MainPresenter implements MainContract.Presenter {
private static final String TAG = "MainPresenter";
//Компоненты MVP програми
private MainContract.View mView;
private MainContract.Repository mRepository;
//Сообщение
private String message;
//Обрати внимание на аргументы конструктора - мы передаем экземпляр View, а Repository просто создаём конструктором.
public MainPresenter(MainContract.View mView) {
this.mView = mView;
this.mRepository = new MainRepository();
Log.d(TAG, "Constructor");
}
//View сообщает, что кнопка была нажата
@Override
public void onButtonWasClicked() {
message = mRepository.loadMessage();
mView.showText(message);
Log.d(TAG, "onButtonWasClicked()");
}
@Override
public void onDestroy() {
/**
* Если бы мы работали например с RxJava, в этом классе стоило бы отписываться от подписок
* Кроме того, при работе с другими методами асинхронного андроида,здесь мы боремся с утечкой контекста
*/
Log.d(TAG, "onDestroy()");
}
}
Пам'ятаєш, я писав про танці з бубном та життєвий цикл? Presenter живе до тих пір, поки живе його View, при розробці складних користувальницьких сценаріїв, раджу дублювати всі колбеки View в Presenter'e і викликати їх у відповідні моменти, дублюючи ЖЦ Activity/Fragment, щоб вчасно зрозуміти, що потрібно зробити з тими даними, які висять зараз у «прошарку». І нарешті, View:
public class MainActivity extends AppCompatActivity implements MainContract.View {
private static final String TAG = "MainActivity";
private MainContract.Presenter mPresenter;
private Button mButton;
private TextView myTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Создаём Presenter и в аргументе передаём ему this - эта Activity расширяет интерфейс MainContract.View
mPresenter = new MainPresenter(this);
myTv = (TextView) findViewById(R.id.text_view);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.onButtonWasClicked();
}
});
Log.d(TAG, "onCreate()");
}
@Override
public void showText(String message) {
myTv.setText(message);
Log.d(TAG, "showMessage()");
}
//Вызываем у Presenter метод onDestroy, чтобы избежать утечек контекста и прочих неприятностей.
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.onDestroy();
Log.d(TAG, "onDestroy()");
}
}
Що ж відбувається?
- Activity, вона ж View, у методі
onCreate()
створює примірник Presenter і передає йому конструктор себе. - Presenter при створенні явно отримує View і створює екземпляр Repository (його, до речі, можна зробити Singleton)
- При натисканні на кнопку View стукає презентеру і повідомляє: «Кнопка була натиснута».
- Presenter звертається до Repository: «Завантаж мені цю шнягу».
- Repository вантажить і віддає "шнягу" Presenter'у.
- Presenter звертається до View: "Ось тобі дані, малюй"
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ