MVP (Model-View-Presenter) usprawnia proces tworzenia ekranów aplikacji poprzez podział odpowiedzialności na trzy rozdzielne warstwy: widoku (View), prezentera (Presenter) oraz modelu (Model). Warstwa widoku odpowiedzialna jest za przechwytywanie interakcji użytkownika i odsyłanie zdarzeń do prezentera, a także za sposób prezentowania danych oraz stanu systemu i wykonywanych operacji w interfejsie graficznym. Warstwa prezentera obsługuje żądania podjęcia akcji na rzecz widoku poprzez proces sprawdzania stanu systemu, walidacji danych, tworzenia zapytań do modelu, a także informowanie widoku o aktualnym stanie zadania. Warstwa modelu zajmuje się logiką biznesową, przetwarzaniem, przechowywaniem oraz dostarczaniem żądanych danych do prezentera. Dzięki zastosowanemu podziałowi odpowiedzialności kod staje się uporządkowany, czytelny, otwarty na modyfikacje, łatwiejszy do debugowania, przeprowadzenia testów oraz ułatwia zespołom jednoczesną pracę nad jednym ekranem. MVP jest przede wszystkim konceptem, nie zbiorem sztywnych reguł implementacji.
Ograniczenia
Ze względu na cykl życia Aktywności, Fragmentu czy innych komponentów interfejsu użytkownika może pojawić się wyciek pamięci referencji do nieistniejącego już widoku w prezenterze. Należy zatem zadbać o odpowiednią obsługę metod cyklu życia. Co więcej odtworzenie stanu prezentera (np. po obrocie ekranu) jest mocno utrudnione. Pomimo zalet płynących z abstrakcji, implementacja wzorca MVP wymaga stworzenia wielu dodatkowych klas i interfejsów (nierzadko o podobnej zawartości) co wiążę się często z nadmiarowym kodem. Ponadto istnieje zagrożenie, że klasa prezentera może stać się superklasą. Jako alternatywę warto rozważyć zastosowanie wzorca MVVM.
Użycie
Wzorzec MVP wykorzystywany jest przede wszystkim w celu zlikwidowania problemu God Activity, czyli Aktywności posiadającej zbyt dużą odpowiedzialność i dużo kodu. Dzięki separacji zadań w różnych warstwach wzorzec może być używany, aby zwiększyć czytelność kodu i możliwość niezależnego testowania logiki biznesowej oraz przepływu interakcji (warstwa prezentera jest niezależna od klas Androidowych).
Implementacja
Zgodnie z podstawowymi założeniami wzorca warstwa widoku ViewImpl implementuje interfejs View oraz inicjalizuje instancje prezentera do którego deleguje wykonanie zadań. Klasa PresenterImpl jest dedykowana dla klasy widoku przez co sama w sobie jest swego rodzaju abstrakcją, dlatego nie ma wymogu, aby dodatkowo implementowała ona interfejs. Klasa PresenterImpl posiada referencje do widoku i modelu dla których pełni rolę pośrednika w komunikacji. Warstwa prezentera jest wolna od zależności klas Androidowych. Klasa Model dostarcza implementację zachowania modyfikacji i pobierania danych.
Poniższy listing przedstawia realizacje wzorca MVP spełniającą minimalne założenia implementacji wraz podstawowym podziałem na warstwy. Rozpoczęcie działania następuje w widoku.
Istnieje wiele wariantów rozszerzenia podstawowej implementacji wzorca MVP, a różnice między nimi są obiektem żywych dyskusji. Popularną praktyką jest tworzenie kontraktu Contract w postaci interfejsów View i Presenter co jasno definiuje oczekiwane zachowania między warstwą widoku i prezentera, umożliwia mockowanie prezentera, a także chroni strukturę klas przed niepowołanym dostępem. Czerpiąc inspiracje ze wzorca VIPERwarstwa modelu jest często realizowana w postaci klasy InteractorImpl, która implementuje interfejs Interactor, a wyniki operacji zwraca do instancji typu OnResult (implementowanego przez PresenterImpl). Dzięki temu warstwa modelu przejmuje część odpowiedzialności warstwy prezentera, która od tego momentu zajmuje się tylko przechwytywaniem zdarzeń z interfejsu użytkownika oraz przygotowaniem danych (pochodzących z interaktora) dla widoku. Aby odciążyć warstwę widoku od odpowiedzialności nawigowania między modułami można wykorzystać w tym celu pomocniczą klasę Router. Gdy w warstwie modelu zachodzi potrzeba wykorzystania obiektu typu Context wówczas dostęp do niego może być realizowany poprzez wstrzykiwanie zależności z poziomu warstwy widoku lub dostarczany ze statycznego kontekstu aplikacji. Ponadto nie należy wiązać prezentera z metodami cyklu życia konkretnych komponentów, a co najwyżej ograniczyć się do metod initialize oraz uninitialize.
Przykład
Ekran logowania ViewActivity w procesie autoryzacji użytkownika wykorzystuje komunikacje sieciową. Zapamiętuje ostatnio poprawnie zalogowanego użytkownika i automatycznie uzupełnia pole loginu. Do implementacji tego procesu wykorzystano wzorzec MVP w wariancie Contract View-Presenter wraz z Interactor jako warstwa modelu.
Biblioteki
Biblioteka Mosby oraz ThirtyInch są przykładem realizacji wzorca MVP. Zmniejszają ilość tworzonego ręcznie kodu oraz automatyzują przywracanie stanu prezentera. Programista implementujący wzorzec MVP powinien pamiętać przede wszystkim o niezależności warstwy prezentera od klas Androidowych, a także o dostosowaniu wariantu rozszerzenia wzorca do realizowanych potrzeb.