[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 tą 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
Dzieki za praktyczne informacje
To ja dziękuję za dobre słowo 😉
O ile przy Xamarin Native MvvmCross to bardzo ciekawa opcja, o tyle w przypadku Xamarin.Forms nie lubię tego frameworka.
Pamiętam moją frustrację kiedy miałem bardzo duże problemy z skonfigurowaniem projektu do wersji 5.1. Po wielu błędach i próbach udało mi się to, jednak kiedy zechciałem zaktualizować do wersji 5.x napotkałem identyczny problem jak twój. W pełni się zgadzam że nie powinno się wprowadzać tak destrukcyjnych zmian w jednej wersji rozwojowej.
W rezultacie przesiadłem się na FreshMvvm. Lekki, prosty i bezproblemowy. Ma wszystko czego potrzebuje, a instalacja i konfiguracja trwa bardzo szybko. Rzadko aktualizowany, ale to dlatego że jest stabilny a większość zgłoszeń na githubie dotyczy pomocy a nie bugów. Pisząc w Xamarin.Formsach trzeba pamiętać że Xamarin ma w zestawie wsparcie dla MVVM, więc nie potrzeba do tego kombajnów które dublują większość mechanizmów (np. MvxCommand i Command).
Dzięki za komentarz.
MvvmCross i Xamarin.Native to zdecydowanie lepsze połączenie – używałem w jednym projekcie i się sprawdziło. Szczególnie przydatne były bindingi jakie MvvmCross udostępnia.
Nad FreshMvvm też zastanawiam się od jakiegoś czasu. Dzięki za podzielenie się opinią.
Do tej pory używaliśmy MVVMLight i w związku z nowym projektem w Xamarin.Forms chciałem spróbować czegoś nowego, wybór początkowo padł na MvvmCross w wersji 5.x (nie pamiętam dokładnej wersji ale bylo to jakieś 3 miesiące temu). Dokumentacja wygladała bardzo przyzwoicie, w końcu pojawil się NavigationService, można zwracać wartości przez ViewModel, bajer. Próbowałem użyć zarówno szablonów w VS jak i kreatora Xablu i za każdym razem nowo stworzony projekt nawet się nie kompilował. Poświęcilem też chwilę na stworzenie pustego projektu w Formsach i dodanie MVVMCross ale proces ten wydał mi się przesadnie skomplikowany, ze względu na ograniczony czas ostatecznie nawet nie odpaliłem zwykłego Hello World.
Po nieudanych próbach z MVVMCross zdecydowałem się wypróbować Prism w wersji 7.0 z .NET Standard 2.0 i w efekcie pusty projekt był gotowy w parę minut, bez żadnych problemów. Z frameworka jak narazie jestem zadowolony, dodaje sporo funkcjonalności, której brakuje w MVVMLight i jednocześnie nie jest tak przytłaczający jak MVVMCross i nie dubluje funkcjonalności Xamarin.Forms.
Cześć Marcinie,
Dzięki za komentarz i przepraszam, że tak późno na niego odpowiadam – niestety utknął w spamie 🙁
Mam dokładnie takie same odczucia co do MvvmCross w wersji 5.x. NavigationService wyglądał bardzo obiecująco i to był m.in. główny powód, dla którego chciałem spróbować nowej wersji frameworka. Proces konfiguracji MvvmCross w nowym projekcie również uważam za dość skomplikowany jeśli robi się to ręcznie. Najwygodniej jest w tym momencie użyć nugetów StarterPack, które konfigurują za nas framework.
Dzięki za podzielenie się swoimi doświadczeniami z używania Prism. Nie miałem jeszcze okazji używać tego frameworka w połączeniu z Xamarinem, ale trochę mnie do tego zachęciłeś 😉
woooo thx – ułatwiasz mi life.