Xamarin.Forms + Prism – Dependency Injection

Wstęp

Dzisiejszy post będzie troszkę nawiązywał do poprzedniego, o dependency injection link.Xamarin Forms nie posiada żadnego wbudowanego mechanizmu do dependency injection więc aby to osiągnąć potrzebujemy dodatkowych bibliotek. Ja w swoim projekcie korzystam z Prism. Autorzy biblioteki utworzyli warstwę abstrakcji Prism.Ioc – jest to zestaw interfejsów do obsługi kontenerów. Dzięki temu rozwiązaniu mogą powstawać pakiety dla kontenerów np. Unity, Ninject itp. Oczywiście jest to tylko teoria, założenie jest słuszne, niestety nie wszystkie kontenery są kompatybilne o czym możemy poczytać w dokumentacji Prism na GitHub – link. Jak już zdecydujemy z którego kontenera chcemy skorzystać to musimy zainstalować odpowiedni pakiet nuget. Ja korzystam z Unity dlatego też przykład będzie w oparciu o ten kontener. Instalujemy paczkę Prism.Forms.Unity. Przedstawiałem już wcześniej jak przygotować projekt do korzystania z Prism – link. Pamiętajmy, że każdy widok z którego chcemy korzystać, musi być zarejestrowany w kontenerze, w metodzie RegisterTypes.

Zależności

Wszystkie zależności rejestrujemy również w metodzie RegisterTypes. Utwórzmy repozytorium IHomeRepository i dodajmy je do naszego HomeViewModelu. Utwórzmy jedną właściwość title i odczytajmy ją z repository.

Ja w przykładzie rozszerzyłem mój view model o interfejs INavigationAware żeby mieć dostęp do metod nawigacji. Kolejny krok to zarejestrowanie IHomeRepository, w App.xaml.cs w metodzie RegisterTypes. W zależności od potrzeb, możemy zarejestrować typ zwyczajnie, jako singleton, a nawet możemy podać konkretną instancję jaka ma być przypisana. TestHomeRepository zwraca po prostu tekst Hello Unity!

Po wykonaniu powyższych kroków, Prism podczas tworzenia obiektu HomeViewModel oraz innych view modeli automatycznie przypisze zarejestrowane wcześniej właściwości.

Przykładowy kod jest dostępny na GitHub – link

Dependency Injection + dotnet core

Podczas pisania kodu czeka nas bardzo dużo problemów do rozwiązania. Pierwsze kawałki kodu zazwyczaj wyglądały tak, że wszystko było pisane w jednej metodzie lub klasie. Następnie, po przeczytaniu paru książek/artykułów, człowiek zaczął bardziej się zastanawiać nad tym co robi. Podział kodu na klasy: repozytoria, managery, serwisy, i wszystkie inne. Następnie pojawia się problem, jak połączyć te klasy ze sobą.

Dependency Injection

Zakładając że mamy klasę CustomersService która korzysta z repozytorium CustomersRepository, to w takim wypadku repository jest zależnością w serwisie. 

Powyższy kod będzie działał ale problem się zacznie pojawiać jeśli będziemy chcieli napisać unit testy albo zmienić data source naszego repository. Z pomocą przychodzi dependency injection, wzorzec który odwraca trochę sytuacje i mówi że mamy przekazywać gotowe komponenty zamiast je tworzyć wewnątrz klasy. Możemy je przekazać na przykład przez konstruktor lub jako właściwości. Przerabiając nasz przykład, zamienimy tworzenie instancji CustomersRepository na przekazanie interfejsu ICustomersRepository.

Po zmianach, możemy swobodnie zacząć pisać unit testy oraz CustomersService nie korzysta teraz z konkretnego CustomersRepository tylko z elastycznego rozwiązania przez interfejs. 

Kontenery

Kontenery są to specjalne klasy które odpowiadają za zarządzanie zależnościami. W skrócie mówiąc, można w nich rejestrować typy oraz tworzyć nowe instancje zarejestrowanych typów. Ja zaprezentuję jak to jest rozwiązanie w aplikacji asp.net core. Utworzyłem domyślny projekt dla aspnet core web api, rozszerzyłem ValuesController żeby korzystał z IValuesRepository oraz utworzyłem klasę implementującą ten interfejs.

Aby powyższy kod zadziałał, musimy jeszcze w aplikacji/kontenerze zarejestrować IValuesRepository. W aspnet core odbywa się to w klasie Startup.cs w metodzie ConfigureServices. Dodajemy jedną linijkę, czyli mówimy że klasa ValuesRepository ma być podstawiona pod IValuesRepository.

Wiele implementacji

W zależności od potrzeb, możemy zarejestrować wiele implementacji jednego interfejsu. Załóżmy że mamy serwis odpowiedzialny za wysyłanie powiadomień, smsy, email i inne. Przykładowa implementacja może wyglądać tak, tworzymy wspólny intefejs IMessageService z metodą Send(string message). Utwórzmy dwie klasy EmailMessageService oraz TextMessageService, obie implementujące IMessageService. Następnie rejestrujemy je w metodzie ConfigureServices i przekazujemy do kontrolera kolekcję typu IEnumerable<IMessageService>.

Powyższa metoda GetMessageServiceTypes() zwróci wszystkie zarejestrowane typy.

Cały kod dostępny na github –
https://github.com/brzooz/Blog/tree/master/c%23/DependencyInjectionExample