MVVM (Model-View-ViewModel) ma za zadanie ułatwić tworzenie ekranów aplikacji poprzez zastosowanie podziału odpowiedzialności na trzy różne warstwy: widoku (View), widoku modelu (ViewModel) oraz modelu (Model). Warstwa widoku odpowiedzialna jest za prezentacje danych, stanu systemu i bieżących operacji w interfejsie graficznym, a także za inicjalizację i wiązanie ViewModel z elementami widoku. Warstwa widoku modelu zajmuje się dostarczaniem danych modelu dla warstwy widoku oraz podejmowaniem akcji na rzecz wywołanego zdarzenia z widoku. Natomiast warstwa modelu odpowiada za logikę biznesową, czyli przetwarzanie, przechowywanie, modyfikacje oraz dostarczanie oczekiwanych danych do widoku modelu. Dzięki zastosowaniu strategii wiązania danych (data binding) w warstwie widoku minimalizowana jest jego logika, kod staje się bardziej uporządkowany i otwarty na modyfikacje, a przeprowadzenie testów łatwiejsze. Idea wzorca MVVM opiera się przede wszystkim na obserwowaniu przez warstwę widoku (wzorzec Obserwator) zmieniających się danych w warstwie widoku modelu i reagowanie na zmiany poprzez mechanizm wiązania danych. Ze względu na różnorodność środowisk i technologii realizacja wzorca MVVM może zostać uzyskana na wiele sposobów.
Ograniczenia
Każdy ekran widoku posiada dedykowany widok modelu co przekłada się na większą ilość klas (w porównaniu do wzorca MVP nadal jest ich mniej). Ponadto warstwa widoku musi zadbać o właściwe wiązanie zmiennych i metod dla każdego wymaganego elementu widoku, a także o obserwowanie stanu widoku modelu. Ze względu na wybór dostępnych technik, bibliotek i komponentów służących realizacji wiązania danych oraz obserwowania ich stanu implementacja wzorca MVVM nie należy do łatwych. Co więcej mnogość strategii implementacji MVVM sprawia, że dane rozwiązanie może być niezrozumiałe dla innych programistów. Stworzenie poprawnego i kompletnego widoku modelu wymaga przeprowadzenia analizy warstwy widoku pod kątem wymaganych danych i możliwych stanów. Zawarty kod logiki kontrolek widoku w plikach layout może być nieczytelny i trudny do testowania, dlatego warto rozważyc umieszczanie pewnych fragmentów logiki w klasie widoku.
Użycie
Wzorzec MVVM jest odpowiedzią na problem God Acticitiy, czyli Aktywności o dużej odpowiedzialności i dużej ilości kodu. Ponadto rozwiązuje on problem wycieku pamięci pojawiający się we wzorcu MVP (widok modelu nie posiada referencji do warstwy widoku). 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 bez użycia dodatkowych zależności. Mechanizm wiązania danych zwiększa elastyczność widoku na modyfikacje co sprawia, że wzorzec MVVM sprawdzi się dobrze w dużych dynamicznie zmieniających się projektach.
Implementacja
Warstwa widoku View tworzy instancje widoku modelu ViewModel oraz wiąże go z widokiem za pomocą mechanizmu data binding. Klasa ViewModel przechowuje wszystkie niezbędne dane dla warstwy widoku, które otrzymuje z modelu. Dzięki zastosowaniu wzorca Obserwator na polach widoku modelu każda zmiana stanu jest odnotowana przez widok. Warstwa modelu Model dostarcza implementacje pobierania, modyfikacji i przetwarzania danych z repozytoriów.
Poniższy listing przedstawia realizację wzorca MVVM z pominięciem zależności Android oraz implementacji wzorca Obserwator.
Implementacja wzorca MVVM może zostać zrealizowana na wiele różnych sposobów i wariantów. Warstwa widoku View może wykorzystywać mechanizm data binding w plikach layout lub w kodzie klasy widoku. Jest również możliwe uzyskanie implementacji wzorca bez użycia wiązania danych, a wszelkie zmiany odnotowane są poprzez śledzenie zmian w ViewModel z wykorzystaniem wzorca Obserwator. Istnieje także wiele podejść do informowania warstwy widoku o wystąpieniu błędu lub postępu operacji. Jedną ze strategii jest traktowanie takich zdarzeń jako pola w klasie widoku modelu i nasłuchiwanie przez widok zmiany stanu i podejmowaniu właściwej akcji. Komunikacja między warstwą modelu Model, a widoku modelu ViewModel może przebiegać przy wykorzystaniu natywnych rozwiązań, funkcji zwrotu (callback), mechanizmu Interactor (znanego z MVP), szyny zdarzeń (event bus), wzorca Obserwator czy też użycia biblioteki do programowania reaktywnego jak np. RxJava. Niezależnie od wyboru strategii implementacji należy przede wszystkim pamiętać, aby widok modelu nie posiadał zależności Android oraz referencji do Aktywności, a warstwa widoku obserwowała stan ViewModel.
Przykład
Aplikacja Scorer ułatwia rejestrowanie i śledzenie zdarzeń z rozgrywek meczu piłkarskiego (zdobyte bramki, żółte i czerwone kartki, kontuzje itp). Obsługujący ją sędzia techniczny wybiera typ wydarzenia oraz wskazuje zawodnika. Dodane wydarzenie automatycznie pojawia się w interfejsie graficznym. Do realizacji tego zadania wykorzystano wzorzec MVVM w wariancie z użyciem komponentów architektury Android.
Biblioteki
Implementacje wzorca MVVM w Androidzie może zostać zrealizowana m.in. za pomocą następujących komponentów architektury: DataBinding, ViewModel oraz LiveData. Ponadto w implementacji nierzadko wykorzystywane są biblioteki RxJava oraz Dagger2.