Kiedy zaczynałem swoją przygodę jako programista Androida, słowa „Architektura aplikacji mobilnych” wywołały u mnie głębokie zdziwienie, Google i artykuły na temat Habré wpędziły mnie w jeszcze większą depresję – patrzę na książkę i nic nie widzę. Myślę, że jeśli czytasz ten artykuł, to już nie raz przestudiowałeś ten obraz i próbowałeś zrozumieć, co się dzieje: moim zdaniem problem zrozumienia podejścia architektonicznego w rozwoju urządzeń mobilnych leży w abstrakcyjności samej architektury. Każdy programista ma własną wizję prawidłowego wdrożenia tego lub innego wzorca. Mniej lub bardziej przyzwoite przykłady wdrożenia MVP znaleziono w anglojęzycznym sektorze Internetu, co nie jest zaskakujące. Przyjrzyjmy się krótko, co jest czym i przejdźmy do przykładu. Model - poziom danych. Nie lubię używać określenia „logika biznesowa”, dlatego w moich aplikacjach nazywam to Repozytorium i komunikuje się z bazą danych i siecią. Widok — poziom wyświetlania. Będzie to widok aktywności , fragmentu lub widoku niestandardowego, jeśli nie lubisz tańczyć z tamburynem i wchodzić w interakcję z cyklem życia. Przypomnę, że początkowo wszystkie aplikacje na Androida podporządkowane są strukturze MVC , gdzie Kontrolerem jest Aktywność lub Fragment . Prezenter jest warstwą pomiędzy Widokiem i Modelem. Widok przesyła zdarzenia, które mu się zdarzają, prezenter przetwarza je, jeśli to konieczne, uzyskuje dostęp do Modelu i zwraca dane do Widoku w celu renderowania. W nawiązaniu do Androida i konkretnego przykładu podkreślę istotną część – Kontrakt. Jest to interfejs opisujący wszystkie interakcje pomiędzy powyższymi komponentami. Podsumowując część teoretyczną:
- Widok wie o Prezenterze;
- Prezenter wie o widoku i modelu (repozytorium);
- Modeluj samodzielnie;
- Kontrakt reguluje interakcje między nimi.
MainContract
:
public interface MainContract {
interface View {
void showText();
}
interface Presenter {
void onButtonWasClicked();
void onDestroy();
}
interface Repository {
String loadMessage();
}
}
Na razie po prostu podkreślamy 3 elementy naszej przyszłej aplikacji i ich działanie. Następnie opiszemy Repozytorium:
public class MainRepository implements MainContract.Repository {
private static final String TAG = "MainRepository";
@Override
public String loadMessage() {
Log.d(TAG, "loadMessage()");
/** Здесь обращаемся к БД Lub сети.
* Я специально ничего не пишу, чтобы не загромождать пример
* DBHelper'ами и прочими не относяшимеся к теме obiektами.
* Поэтому я буду возвращать строку Сосисочная =)
*/
return "Сосисочная у Лёхи»;
}
}
Wszystko jest z nim jasne, wystarczy załadować i rozładować dane. Następny w kolejności jest prezenter:
public class MainPresenter implements MainContract.Presenter {
private static final String TAG = "MainPresenter";
//Компоненты MVP Aplikacje
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()");
}
}
Pamiętacie, jak pisałam o tańcu z tamburynem i cyklu życia? Prezenter żyje tak długo, jak żyje jego Widok. Podczas opracowywania złożonych scenariuszy użytkownika radzę zduplikować wszystkie wywołania zwrotne Widoku w Prezenterze i wywołać je w odpowiednich momentach, powielając cykl życia Aktywności/Fragmentu, aby z czasem zrozumieć, co należy zrobić z danymi, które aktualnie wiszą w „międzywarstwie”. I na koniec widok:
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()");
}
}
Co się dzieje?
- Działanie, znane również jako Widok,
onCreate()
tworzy instancję Presenter w metodzie i przekazuje się do jej konstruktora. - Kiedy tworzony jest prezenter, jawnie otrzymuje widok i tworzy instancję repozytorium (nawiasem mówiąc, można go przekształcić w Singleton)
- Po naciśnięciu przycisku View puka do prezentera i mówi: „Przycisk został naciśnięty”.
- Prezenter zwraca się do Repozytorium: „Pobierz dla mnie te bzdury”.
- Repozytorium ładuje i dostarcza „materiały” Prezenterowi.
- Prezenter odwraca się do Widoku: „Oto dane dla Ciebie, narysuj je”
GO TO FULL VERSION