W tym wpisie przyjrzymy się bliżej podstawowym elementom biblioteki Koin. Po przeczytaniu tego materiału będziesz w stanie:
- stworzyć moduł
- zadeklarować zależność z użyciem
factory
- zadeklarować zależność z użyciem
single
– singleton - pobrać zależność przy pomocy
get
- uruchomić Koina
Ten post jest kontynuacją serii “Koin bez tajemnic”. Jeśli nie czytałeś poprzednich artykułów to poniżej znajdziesz do nich odnośniki.
- Wprowadzenie do dependency injection
- Tworzenie modułu i deklarowanie zależności (aktualnie czytany)
Tworzenie modułu
Moduł w bibliotece Koin możemy rozumieć jako element, który decyduje o tym, w jaki sposób wstrzykiwane są zależności. W obrębie jednej aplikacji jesteśmy w stanie deklarować dowolną ich liczbę. Dobrą praktyką jest tworzenie modułów, które w logiczny sposób grupują znajdujące się wewnątrz części składowe. Gdy pracujemy nad ekranem logowania możemy zadeklarować wszystkie zależności w module o nazwie loginModule
. Jeśli natomiast wiemy, że niektóre obiekty będą używane w obrębie całej aplikacji możemy stworzyć moduł dedykowany do takich zależności – appModule
.
Jednym ze sposobów na stworzenie nowego modułu jest w pierwszej kolejności zdefiniowanie zmiennej, która będzie przechowywać do niego referencję. W następnym kroku możemy wywołać funkcję module
do zadeklarowania zależności. Spójrzmy na na przykład:
Widzimy, że do finalnej zmiennej appModule
został przypisany moduł. Najtrudniejsze w tym całym procesie wydaje się (jak to zazwyczaj bywa) wymyślenie odpowiedniej nazwy, bo reszta jest prosta i intuicyjna. Przestrzeń między wąsatymi nawiasami może zostać teraz wypełniona deklaracjami potrzebnych zależności.
Deklarowanie zależności
Deklarowanie zależności przy użyciu biblioteki Koin może zostać zrealizowane na kilka sposobów. Podstawowymi funkcjami, które do tego służą, są factory
oraz single
. To co je od siebie odróżnia, to sposób w jaki tworzą obiekty. Czym w takim razie charakteryzuje się każda z wymienionych metod?
Factory
Obiekty zadeklarowane przy pomocy funkcji factory
będą tworzone na nowo dla każdej klasy, która z nich korzysta. Jak to wygląda w praktyce? To tak jakbyśmy za każdym razem wywoływali konstruktor do stworzenia potrzebnego obiektu. Dzięki temu, że korzystamy z narzędzia do wstrzykiwania zależności nie musimy tego robić ręcznie tylko ta odpowiedzialność leży po stronie biblioteki.
Single
Jak sama nazwa sugeruje, użycie funkcji single
przy deklarowaniu zależności będzie skutkować tym, że zostanie utworzona tylko jedna instancja obiektu danej klasy. Czy wiesz już z jakim wzorcem projektowym mamy tutaj do czynienia? Zgadza się, zadeklarowane w ten sposób obiekty będą singletonem. Nie musimy tworzyć implementacji tego wzorca ręcznie, biblioteka zadba o to samodzielnie.
Pobieranie zależności
Podczas uruchomienia biblioteki Koin (o tym jak to zrobić dowiesz się w jednym z kolejnych rozdziałów) tworzy się tzw. drzewo zależności, które jest odzwierciedleniem tego, co zostało wcześniej zadeklarowane w modułach. Jak się pewnie domyślasz sama definicja zależności nie wystarczy do poprawnego działania aplikacji, trzeba jeszcze gdzieś z tych zależności skorzystać.
Get
Funkcja get
służy do tego, żeby pobrać odpowiednią zależność z drzewa i umieścić ją w miejscu jej użycia. Jej działanie jest natychmiastowe tzn., że w momencie wywołania od razu zwraca obiekt konkretnego typu (o ile znajdzie go w drzewie). Głównie stosuje się ją w modułach w celu pobrania obiektu, który jest potrzebny do stworzenia innej zależności.
Innymi funkcjami do pobrania zależności są inject
oraz bind
ale zostaną one szczerzej omówione w kolejnych wpisach. Warto w tym momencie wspomnieć, że gdy którakolwiek z wyżej wymienionych funkcji nie znajdzie deklaracji z konkretnym typem, to będzie to skutkowało crashem aplikacji.
Przykład użycia
Połączmy ze sobą zdobytą do tej pory wiedzę i przedstawmy powyższe sposoby wstrzykiwania zależności na przykładowym module. Do prezentacji posłużą nam następujące klasy i interfejsy:
- Interfejs
Api
służący do komunikacji z zewnętrznym RESTowym serwerem. - Klasa
UserRepositoryImpl
, która implementuje interfejsUserRepository
.
- Dwie klasy Use Case. Jedna do logowania –
LoginUseCase
, a druga do rejestracji użytkownika –RegisterUseCase
. Zauważ, że jako argument jest tam przekazywany interfejs repozytorium, a nie konkretna implementacja.
Jak zatem będzie wyglądał moduł dla powyższych elementów aplikacji? Zacznijmy może od zdefiniowania zależności dla warstwy sieciowej.
Widzimy, że przy użyciu pomocniczych funkcji zostały zdefiniowane dwie zależności. Obie z nich zostały oznaczone jako single
, czyli w rezultacie tylko jedna instancja każdego z tych obiektów powstanie podczas działania aplikacji. Często warstwa sieciowa jest współdzielona między innymi komponentami aplikacji dlatego zaprojektowanie modułu w ten sposób ma swoje uzasadnienie.
Warto też zwrócić uwagę na użycie funkcji get
, która została przekazana jako argument do innej funkcji tworzącej konkretną zależność – provideApi
. Z racji tego, że obiekt typu Retrofit
został już zadeklarowany wcześniej, to istnieje możliwość odszukania go w drzewie zależności i użycia w tym miejscu.
Przejdźmy do dalszej części przykładu. Zostały nam jeszcze do zdefiniowania klasy służące do logowania i rejestracji. Z racji tego, że obie operacje dotyczą użytkownika nasz moduł będzie nosił nazwę userModule
.
Widzimy, że tym razem zależności zostały zadeklarowane przy pomocy funkcji factory
. Oznacza to, że w każdym miejscu, gdzie będzie potrzeba ich użycia, zostaną stworzone nowe instancje obiektów tych klas. Sama struktura zdefiniowania takiej zależności jest bliźniaczo podobna do tej, którą poznaliśmy w przypadku single
.
W powyższym module istnieje jednak jedna deklaracja, która nieco różni się swoją budową od pozostałych – UserRepositoryImpl
. Zapis, który widzisz w przykładzie oznacza, że wymuszamy, aby klasa ta została zadeklarowana jako interfejs UserRepository
. Możemy tak zrobić, bo jak dobrze pamiętasz klasa UserRepositoryImpl
implementowała ten właśnie interfejs. W taki właśnie sposób, możemy przypisywać interesujące nas implementacje do konkretnego interfejsu korzystając z modułów w Koinie.
Uruchomienie Koina
Do uruchomienia Koina służy funkcja startKoin
i powinna ona być wywołana z poziomu klasy Application
.
Obowiązkowym elementem, który musi znaleźć się między wąsatymi nawiasami jest funkcja modules
przy pomocy której wskazujemy, które moduły mają zostać użyte do stworzenia drzewa zależności. Kolejnym istotnym elementem jest przekazanie context’u aplikacji. Jest to możliwe dzięki funkcji androidContext
. Wywołując później tę samą funkcję z poziomu modułu, możemy przekazać context aplikacji w miejsca, gdzie istnieje taka potrzeba. Jeśli chcemy, aby w konsoli wyświetlały nam się logi z biblioteki, to możemy taką opcję odblokować używając androidLogger
. Pamiętaj tylko, że ze względów bezpieczeństwa nie powinno się publikować logów w releasowych wersjach aplikacji.
Ciąg dalszy
Jak wspomniałem na początku ten artykuł jest elementem serii “Koin bez tajemnic”. Kolejne artykuły pojawią się wkrótce. Jeśli wpis Ci się podobał, to będzie mi niezwykle miło jak udostępnisz go w mediach społecznościowych.
Świetny artykuł!