Articles

Android Architecture Patterns Part 2:Model-View-Presenter

Florina Muntenescu
Florina Muntenescu

Follow

Nov 2, 2016 · 6 min read

It’s about time we developers start thinking about how we can apply good architecture patterns in our Android apps. Aby pomóc w tym, Google oferuje plany architektury Androida, w których Erik Hellman i ja pracowaliśmy razem nad MVP & próbka RxJava. Przyjrzyjmy się temu, jak go zastosowaliśmy oraz zaletom i wadom tego podejścia.

oto role każdego komponentu:

  • Model — Warstwa danych. Odpowiedzialny za obsługę logiki biznesowej oraz komunikację z warstwami sieciowymi i bazodanowymi.
  • View-warstwa interfejsu użytkownika. Wyświetla dane i powiadamia prezentera o działaniach użytkownika.
  • prezenter-pobiera dane z modelu, stosuje logikę interfejsu użytkownika i zarządza stanem widoku, decyduje, co ma być wyświetlane i reaguje na powiadomienia wejściowe użytkownika z widoku.

ponieważ widok i prezenter ściśle ze sobą współpracują, muszą mieć do siebie odniesienie. Aby moduł prezentera mógł być testowany za pomocą JUnit, widok jest abstrakcyjny i używany jest dla niego interfejs. Relacja między prezenterem a odpowiadającym mu widokiem jest zdefiniowana w klasie interfejsuContract, dzięki czemu kod jest bardziej czytelny i połączenie między nimi jest łatwiejsze do zrozumienia.

Model-struktura klasy view-Presenter

wzór modelu view-Presenter&RxJava w architekturze Android

przykładowy schemat jest aplikacją „do zrobienia”. Pozwala użytkownikowi tworzyć, czytać, aktualizować i usuwać zadania „do zrobienia”, a także stosować filtry do wyświetlonej listy zadań. RxJava służy do odsunięcia głównego wątku i obsługi operacji asynchronicznych.

Model

model współpracuje ze zdalnymi i lokalnymi źródłami danych, aby uzyskać i zapisać dane. To tutaj odbywa się logika biznesowa. Na przykład, podczas żądania listy Tasks, Model będzie próbował pobrać je z lokalnego źródła danych. Jeśli jest pusta, zapyta sieć, zapisze odpowiedź w lokalnym źródle danych, a następnie zwróci listę.

pobieranie zadań odbywa się za pomocą RxJava:

public Observable<List<Task>> getTasks(){ 
...
}

Model odbiera jako parametry w interfejsach konstruktora lokalnych i zdalnych źródeł danych, czyniąc Model całkowicie niezależnym od jakichkolwiek klas Androida, a tym samym łatwym do jednostkowego testowania z Junitem. Na przykład, aby sprawdzić, czy getTasks żąda danych ze źródła lokalnego, zaimplementowaliśmy następujący test:

@Mock 
private TasksDataSource mTasksRemoteDataSource;
@Mock
private TasksDataSource mTasksLocalDataSource;
...
@Test
public void getTasks_requestsAllTasksFromLocalDataSource() {
// Given that the local data source has data available
setTasksAvailable(mTasksLocalDataSource, TASKS);
// And the remote data source does not have any data available
setTasksNotAvailable(mTasksRemoteDataSource);
// When tasks are requested from the tasks repository
TestSubscriber<List<Task>> testSubscriber =
new TestSubscriber<>();
mTasksRepository.getTasks().subscribe(testSubscriber); // Then tasks are loaded from the local data source
verify(mTasksLocalDataSource).getTasks();
testSubscriber.assertValue(TASKS);
}

Widok

Widok współpracuje z prezenterem, aby wyświetlić dane i powiadamia prezentera o działaniach użytkownika. W działaniach MVP fragmenty i niestandardowe widoki Androida mogą być widokami. Wybraliśmy użycie fragmentów.

wszystkie widoki implementują ten sam interfejs BaseView, który umożliwia ustawienie prezentera.

public interface BaseView<T> {
void setPresenter(T presenter);
}

Widok powiadamia prezentera, że jest gotowy do aktualizacji, wywołującsubscribemetodę prezentera wonResume. Widok wywołuje presenter.unsubscribe() w onPause, aby poinformować prezentera, że nie jest już zainteresowany aktualizacją. Jeżeli implementacją widoku jest własny widok Androida, wtedy subscribe I unsubscribe należy wywołać metody onAttachedToWindow I onDetachedFromWindow. Działania użytkownika, takie jak kliknięcia przycisków, wywołają odpowiednie metody w prezenterze, które decydują o tym, co powinno się stać dalej.

widoki są testowane z Espresso. Na przykład ekran statystyk musi wyświetlać liczbę aktywnych i ukończonych zadań. Test sprawdzający, czy zostało to wykonane poprawnie, najpierw umieszcza niektóre zadania w TaskRepository; następnie uruchamia StatisticsActivity I sprawdza zawartość widoków:

@Before 
public void setup() {
// Given some tasks
TasksRepository.destroyInstance();
TasksRepository repository = Injection.provideTasksRepository( InstrumentationRegistry.getContext()); repository.saveTask(new Task("Title1", "", false));
repository.saveTask(new Task("Title2", "", true)); // Lazily start the Activity from the ActivityTestRule
Intent startIntent = new Intent();
mStatisticsActivityTestRule.launchActivity(startIntent);
}@Test
public void Tasks_ShowsNonEmptyMessage() throws Exception {
// Check that the active and completed tasks text is displayed
Context context = InstrumentationRegistry.getTargetContext();
String expectedActiveTaskText = context
.getString(R.string.statistics_active_tasks);
String expectedCompletedTaskText = context
.getString(R.string.statistics_completed_tasks); onView(withText(containsString(expectedActiveTaskText)))
.check(matches(isDisplayed()));
onView(withText(containsString(expectedCompletedTaskText)))
.check(matches(isDisplayed()));
}

prezenter

prezenter i odpowiadający mu Widok są tworzone przez działanie. Odniesienia do widoku i doTaskRepository – Model – są podane konstruktorowi prezentera. W implementacji konstruktora, prezenter wywołasetPresenter metodę widoku. Można to uprościć przy użyciu struktury wstrzykiwania zależności, która umożliwia wstrzykiwanie prezenterów w odpowiednich widokach, zmniejszając łączenie klas. Implementacja ToDo-MVP ze sztyletem jest objęta inną próbką.

wszyscy Prezenterzy implementują ten sam interfejs BasePresenter.

public interface BasePresenter {
void subscribe();
void unsubscribe();
}

gdy zostanie wywołana metodasubscribe, prezenter zaczyna żądać danych z modelu, następnie stosuje logikę interfejsu użytkownika do odebranych danych i ustawia je na widok. Na przykład wStatisticsPresenter wszystkie zadania są wymagane zTaskRepository – wtedy pobrane zadania są używane do obliczenia liczby aktywnych i ukończonych zadań. Te liczby będą użyte jako parametry dlashowStatistics(int numberOfActiveTasks, int numberOfCompletedTasks) metody widoku.

test jednostkowy, aby sprawdzić, czy rzeczywiście metoda showStatistics jest wywoływana z poprawnymi wartościami, jest łatwa do zaimplementowania. Wyśmiewamy TaskRepository I StatisticsContract.View I dajemy wyśmiane obiekty jako parametry konstruktorowi StatisticsPresenter obiektu. Implementacja testowa to:

@Test 
public void loadNonEmptyTasksFromRepository_CallViewToDisplay() {
// Given an initialized StatisticsPresenter with
// 1 active and 2 completed tasks
setTasksAvailable(TASKS); // When loading of Tasks is requested
mStatisticsPresenter.subscribe(); // Then the correct data is passed on to the view
verify(mStatisticsView).showStatistics(1, 2);
}

rolą metodyunsubscribe jest wyczyszczenie wszystkich subskrypcji prezentera, unikając w ten sposób wycieków pamięci.

opróczsubscribe Iunsubscribe, każdy prezenter udostępnia inne metody, odpowiadające działaniom użytkownika w widoku. Na przykład AddEditTaskPresenter, dodaje metody takie jak createTask, które zostaną wywołane, gdy użytkownik naciśnie przycisk, który utworzy nowe zadanie. Gwarantuje to, że wszystkie działania użytkownika – a co za tym idzie cała logika interfejsu – przechodzą przez prezentera, a tym samym mogą być testowane jednostkowo.

wady wzorca Model-View-Presenter

wzorzec Model-View-Presenter niesie ze sobą bardzo dobrą separację problemów. Chociaż jest to na pewno profesjonalista, podczas opracowywania małej aplikacji lub prototypu może to wydawać się narzutem. Aby zmniejszyć liczbę używanych interfejsów, niektórzy programiści usuwają klasę interfejsuContract oraz interfejs dla prezentera.

jedna z pułapek MVP pojawia się podczas przenoszenia logiki interfejsu użytkownika do prezentera: staje się ona teraz klasą wszechwiedzącą, z tysiącami linii kodu. Aby to rozwiązać, podziel Kod jeszcze bardziej i pamiętaj, aby tworzyć klasy, które mają tylko jedną odpowiedzialność i są testowalne jednostkowo.

wniosek

wzór Model-widok-kontroler ma dwie główne wady: po pierwsze, Widok ma odniesienie zarówno do kontrolera, jak i modelu; po drugie, nie ogranicza obsługi logiki interfejsu użytkownika do jednej klasy, odpowiedzialność ta jest dzielona między kontroler a Widok lub Model. Wzorzec Model-Widok-prezenter rozwiązuje oba te problemy, zrywając połączenie widoku z modelem i tworząc tylko jedną klasę, która obsługuje wszystko, co związane jest z prezentacją widoku-prezenter: jedną klasę, którą można łatwo przetestować jednostkowo.

Co jeśli chcemy architekturę opartą na zdarzeniach, w której Widok reaguje na zmiany? Wypatrujcie kolejnych wzorców próbkowanych w schematach architektury Androida, aby zobaczyć, jak można to wdrożyć. Do tego czasu przeczytaj o naszej implementacji wzorca Model-View-ViewModel w aplikacji upday.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *