[PL] Doświadczenia: MvvmCross + Xamarin.Forms

Wstęp

Nie ulega wątpliwości, że MvvmCross to dobry framework tworzony przez zespół świetnych programistów przy pomocy zaangażowanej społeczności. Co więcej, powstało napewno wiele komercyjnych projektów odnoszących sukcesy, które używają właśnie MvvmCross. Popularności frameworkowi nie da się odmówić – jest na ustach praktycznie każdej osoby zajmującej się tworzeniem aplikacji w Xamarinie.

W naszym projekcie, bazującym na Xamarin.Forms, również chcieliśmy użyć MvvmCross. W tym wpisie opiszę swoje doświadczenia z używania wspomnianego frameworka w wersji 5.1.1.

Dla niecierpliwych stworzyłem sekcję TL;DR, do której serdecznie zapraszam, jeśli nie masz zbyt dużo czasu na czytanie 🙂

Do czego właściwie używamy MvvmCross?

W moim odczuciu, framework powinien przyśpieszać tworzenie aplikacji oraz dostarczać funkcjonalności, które musielibyśmy w innym przypadku napisać sami. Należałoby również oczekiwać szybkiej i prostej integracji frameworka z naszą aplikacją. Inną istotną kwestią, jest dokumentacja jaką dany framework posiada – czy taka dokumentacja w ogóle jest oraz na ile jest ona przydatna. Warto również sprawdzić jak często framework jest aktualizowany przez jego twórców oraz w jakim kierunku będzie rozwijany w przyszłości.

Od frameworka MVVM na pewno moglibyśmy oczekiwać dwóch podstawowych funkcjonalności:
– wsparcia dla nawigacji pomiędzy widokami
– kontenera IoC

Oprócz wspomnianych rzeczy, korzystamy jeszcze z paru funkcjonalności jakie zapewnia sam MvvmCross:
MvxAsyncCommand
Messenger

Z kolei, jeśli spojrzymy na dokumentację oraz na kod źródłowy zobaczymy, że dostępnych funkcjonalności jest jeszcze wiele więcej. Zacząłem się zastanawiać czy na pewno potrzebujemy używać aż tak rozbudowanego frameworka, jeśli korzystamy tylko z paru jego funkcjonalności.

Problemy

Spory próg wejścia

W poszukiwaniu informacji jak zacząc pracę z MvvmCross, możemy trafić na stronę oficjalnej dokumentacji, ale informacje tam zawarte dotyczą wersji 4 podczas gdy dostępna jest już wersja 5. Z kolei na tej stronie możemy znaleźć listę dostępnych paczek MvvmCross. Jednymi z takich paczek są paczki “StarterPack”, które konfigurują projekt do działania z MvvmCross. Po instalacji takiej paczki do projektu dodawany jest m.in. plik TODO, w którym zawarte są dalsze instrukcje jakie należy wykonać w celu dokończenia konfiguracji. Jednak na próżno w dokumentacji szukać informacji jak samemu od początku do końca skonfigurować projekt do działania z frameworkiem.

W dalszej kolejności na pewno warto zapoznać się z tymi stronami dokumentacji:
MvvmCross Overview
ViewModel Lifecycle
Navigation
View Presenters
Inversion of Control
Dependency Injection
Customizing App and Setup

Jest tego trochę, co nie? Oprócz tego możemy zajrzeć do kodu źródłowego, gdzie znajdziemy projekt Playground czyli przykładowe aplikacje korzystające z MvvmCross.

Moim zdaniem na początku pracy z frameworkiem można się trochę pogubić. W dokumentacji brakuje, według mnie, tutoriala, który przeprowadzi nas kroczek po kroczku od najprostszych rzeczy – zamiast tego zostajemy rzuceni na głęboką wodę i może zająć nam trochę czasu, aż odnajdziemy się w tym wszystkim.

Myślę, że warto zastanowić się nad wsparciem społeczności MvvmCross i wzięcia sprawy w swoje ręce w celu poprawienia dokumentacji. Zyskają na tym wszyscy korzystający z MvvmCross. Przykładów takich działań daleko szukać nie trzeba i można wspomnieć tutaj o Danielu Krzyczkowskim, który na swoim GitHubie rozwija przykładowe projekty właśnie dla MvvmCross.

Brak kompatybilności wstecznej

Wspomniałem na początku, że korzystamy z MvvmCross w wersji 5.1.1. Obecnie dostępna jest już wersja 5.6. Do tej właśnie wersji chcieliśmy zaktualizować framework, ale projekt przestał się kompilować. Łącznie naliczyłem 150 błędów przy kompilacji. Cześć błędów to były zmiany w namespace, to była ta łatwa część do naprawienia. Niestety pozostała cześć dotyczyła zmian dostępnego API w MvvmCross.

Postanowiliśmy jednak nie aktualizować MvvmCross do najnowszej wersji z powodu braku czasu. Z drugiej strony dla tej samej gałęzi rozwojowej projektu (5.x), nie powinno być sytuacji, kiedy nowsza wersja zrywa z kompatybilnością wsteczną. Sprawia to wrażenie niestabilności projektu. Co jeśli za jakiś czas będę ponownie chciał zaktualizować framework do najnowszej wersji? Znowu pojawią się błędy kompilacji? Znowu będę musiał poprawiać projekt? Chyba nie tędy droga.

Utrudnione stworzenie bazowego view modelu

MvvmCross posiada obecnie kilka view modeli, po których możemy dziedziczyć:
– MvxViewModel – “zwykły” view model
– MvxViewModel<TParameter> – view model, który przyjmuje parametr podczas nawigacji
– MvxViewModelResult<TResult> – view model, który zwraca rezultat po jego zamknięciu
– MvxViewModel<TParameter, TResult> – view model, który przyjmuje parametr i zwraca rezultat

Co jeśli chcielibyśmy stworzyć własny bazowy view model z określoną funkcjonalnością potrzebną we wszystkich view modelach jakie tworzymy? Sytuacja staje się trochę problematyczna i zamiast po prostu stworzyć własną klasę bazową jesteśmy zmuszeni zaimplementować takie rozwiązanie w zupełnie inny sposób (np. wydzielić wspólną funkcjonalność do komponentu).

Odpinanie się MvxCommand od przycisków

Natrafiliśmy w aplikacji na przypadek, że przyciski na widoku przestawały działać kiedy nawigowaliśmy się do kolejnego widoku i wracaliśmy. Czyli dla przykładu mamy widok A oraz widok B. Z widoku A nawigowaliśmy do widoku B. Z widoku B wracaliśmy z powrotem do widoku A. Taka akcja była wykonywana parę razy. Za którymś razem przyciski na widoku A przestawały działać.

Poświęciłem chwilę czasu na debugowanie MvvmCross i problemem okazało się domyślne użycie klasy MvxWeakCommandHelper w MvxSetup w metodzie InitializeCommandHelper. Nadpisałem tą metodę w naszej klasie Setup na każdej platformie (Android oraz iOS) i użyłem klasy MvxStrongCommandHelper. Poniżej kod:

protected override void InitializeCommandHelper()
{
    Mvx.RegisterType<IMvxCommandHelper,MvxStrongCommandHelper>();
}

Po takiej zmianie problem zniknął.

Brak możliwości użycia await w presenterach

MvvmCross posiada ciekawą koncepcję presenterów. W dużym skrócie polega to na separacji view modeli od sposobu prezentowania danych na widoku. W założeniach koncepcja mi się podoba, ale brakuje, moim zdaniem, możliwości użycia await. Przykładowo, klasa MvxFormsPagePresenter posiada publiczne metody typu void. Mimo tego, że samo Xamarin.Forms dostarcza interfejs INavigation pozwalający na użycie await.

Czemu właściwie o tym piszę? Już wyjaśniam. Pod jednym przyciskiem w aplikacji mieliśmy zrobioną nawigację do nowego widoku. Okazało się, że bardzo szybkie naciskanie przycisku powodowało kilkukrotną nawigację do tego samego widoku. Niestety, nie było sposobu na poradzenie sobie z tym problemem przy użyciu mechanizmów dostarczanych przez MvvmCross.

Musieliśmy stworzyć własny serwis do nawigacji (dziedziczący po MvxNavigationService) oraz własnego presentera (dziedziczący po MvxFormsPagePresenter). We własnym presenterze mogliśmy dodać metody asynchroniczne, które były następnie wykorzystane przez naszą implementacje serwisu nawigacyjnego. W ten sposób byliśmy w stanie ignorwać kolejne próby nawigacji dopóki poprzednia nawigacja się nie skończyła.

Crash na Androidzie po dłuższym powrocie do aplikacji

Wersja naszej aplikacji na Androida crashowała się przy powrocie do niej kiedy nie była ona używana przez dłuższy czas. Czyli kiedy system operacyjny usunął ją z pamięci i aplikacja musiała uruchomić się ponownie. Przy normalnym uruchomieniu aplikacji (kiedy nie ma jej w pamięci oraz na liście uruchomionych aplikacji), wywoływana jest najpierw klasa SplashScreen, dziedzicząca po MvxSplashScreenActivity. Celem tej klasy jest inicjalizacja MvvmCross. Po inicjalizacji frameworka zostaje uruchomiony właściwy Activity (dziedziczący po MvxFormsAppCompatActivity), który uruchamia naszą aplikację. Możemy na potrzeby przykładu nazwać ten Activity jako MainActivity.

Jednak w przypadku powrotu do aplikacji po dłuższym czasie, klasa SplashScreen się nie wywoływała. Aplikacja od razu przechodziła do MainActivity, a następnie cała aplikacja się crashowała z powodu braku zainicjalizowania MvvmCross.

Po dłuższym debugowaniu i grzebaniu w kodzie MvvmCross doszedłem do rozwiązania, które polegało m.in. na wykonaniu poniższego kodu w metodzie OnCreate w MainActivity:

MvxAndroidSetupSingleton
    .EnsureSingletonAvailable(this.ApplicationContext)
    .EnsureInitialized();

Zapewnia to poprawne zainicjalizowanie MvvmCross.

TL;DR

Moje problemy z MvvmCross w połączeniu z Xamarin.Forms:

  • Spory próg wejścia
  • Brak kompatybilności wstecznej dla tej samej gałęzi rozwojowej
  • Utrudnione stworzenie bazowego view modelu (MvvmCross dostarcza 4 różne view modele)
  • Odpinanie się MvxCommand od przycisków podczas nawigacji
  • Brak możliwości użycia await w presenterach
  • Crash na Androidzie po dłuższym powrocie do aplikacji

Podsumowanie

Na pewno cześć problemów, z którymi miałem do czynienia zostały już poprawione w najnowszej wersji. Jednak w obecnej fazie projektu wysiłek w uaktualnieniu do najnowszej wersji MvvmCross jest zbyt duży i nie opłacalny. Nie mamy również pewności czy najnowsza wersja frameworka nie wprowadzana dodatkowych błędów. Na razie zostajemy przy wersji 5.1, która została w naszym projekcie ustabilizowana, a jak coś działa to lepiej tego nie ruszać. W końcu framework powinien wspierać nas w pisaniu aplikacji, a w przypadku MvvmCross miałem wręcz odwrotne odczucia.

Nasuwa mi się dodatkowe przemyślenie, że część problemów jakie mieliśmy z MvvmCross wynika z samej jego natury. Jest to w końcu framework tworzony dla wielu różnych platform, które muszą być wspierane na bieżąco. Utrzymanie oraz zaprojektowanie frameworka wspierającego tyle różnych platform to na pewno wielkie wyzwanie i ogrom pracy. Mam wrażenie, że twórcy miejscami musieli pójść na kompromisy przy tworzeniu projektu. Zakładam, że właśnie z tych powód mogliśmy mieć opisane w tym poście problemy.

Dajcie koniecznie znać w komentarzach jakie są wasze doświadczenia w używaniu MvvmCross w projekcie bazującym na Xamarin.Forms.

 

[aktualizacja 03.02.18] – jakiś czas temu na Twiterze dostałem odpowiedź od twórców MvvmCross odnośnie moich doświadczeń. Zachęcam do zapoznania się: https://twitter.com/D_Antonowicz/status/951200103603286017