Akademia Lekcje
- Lekcja 1 – Activity (link)
- Lekcja 2 – MVVM (link)
- Lekcja 3 – Obsługa Bazy Danych – Room (link)
- Lekcja 4 – Wdrożenie DI (jesteś tutaj)
Witaj w drugiej części artykułu poświęconemu Bibliotece Koin. Tak jak zapowiadałem, w tej publikacji wdrożymy Koina do naszego projektu. Jeśli jeszcze nie wiesz czym jest wspomniana biblioteka i do czego służy to wpadnij do wcześniejszego artykułu pod TYM ADRESEM [LINK].
Czego potrzebujemy?
By poprawnie zaimplementować naszą bibliotekę, musimy:
- Dodać zależność biblioteki do pliku build.gradle:
implementation „org.koin:koin-androidx-viewmodel: 2.0.1”
- Stworzyć moduły, w których zadeklarujemy niezbędne zależności
- Wstrzyknąć zależności w odpowiednie miejsca
- Uruchomić Koina
Co wstrzykiwać?
Na początek, pomyślmy jakie zależności chcielibyśmy wstrzykiwać i dlaczego.
Zależność #1 Baza danych
Teraz, definiujemy naszą bazę danych w klasie FilmsApplication
i za pośrednictwem zmiennej database
otrzymujemy do niej dostęp w dowolnym miejscu aplikacji. Jak się domyślasz, nie jest to ładne rozwiązanie. Dlaczego? Bo łamiemy jedną z głównych zasad SOLID.
Klasa powinna koncentrować się na wypełnianiu swoich obowiązków, a nie na tworzeniu podmiotów, niezbędnych do ich wykonania.
class FilmsApplication : Application() {
override fun onCreate() {
super.onCreate()
database = Room.databaseBuilder(
this,
FilmsDatabase::class.java,
"films_database"
).build()
}
companion object {
lateinit var database: FilmsDatabase
}
}
Już za chwilę, przeniesiemy ją do odpowiedniego modułu DI.
Zależność #2 DAO
Jak widzisz, w klasie FilmsRepository
definiujemy nasz obiekt DAO
.
class FilmsRepository {
/* TODO Should be injected by DI :) */
private val filmsDao: FilmsDao =
FilmsApplication.database.filmDao()
By zachować jak najwyższą czystość kodu, klasę FilmsDao
zdefiniujemy w module DI i wstrzykniemy ją do konstruktora klasy FilmsRepository
. Efekt poniżej 🙂 Wygląda to ładniej, prawda?
class FilmsRepository(
private val filmsDao: FilmsDao
) { ... }
Zależność #3 Repository
Obecnie, definiujemy naszą klasę FilmsRepository
w konstruktorze klasy FilmsViewModel
.
class FilmsViewModel(
val repository: FilmsRepository = FilmsRepository()
) : ViewModel() { ... }
Zależność #4 ViewModel
Mimo, że klasa FilmsViewModel
definiowana jest w poprawny sposób i z pozoru nie ma potrzeby jej wstrzykiwać, pokusimy się na to rozwiązanie. Będzie nam znacznie łatwiej zapanować nad tym kodem w późniejszym czasie.
class FilmsActivity : AppCompatActivity() {
private val viewModel: FilmsViewModel by viewModels()
Wdrożenie DI – Tworzenie modułów
Ustaliliśmy, że chcemy wyekstrahować:
Bazę danych
FilmsDao
FilmsRepository
FilmsViewModel
W naszej aplikacji, FilmsDao
oraz FilmsRepository
są powiązane z bazą danych. Dlatego śmiało możemy zdefiniować te trzy zależności w jednym module (nazwanej przykładowo dbModule
). Dla klasy FilmsViewModel
utworzymy nowy moduł (np. filmsModule, appModule lub viewModelsModule. Wszystko zależy od architektury plików jaką wybierzesz).
Moduł #1 – zależności powiązane z bazą danych
Na początek stwórzmy oddzielny plik DbModule.kt
z modułem o nazwie dbModule
.
val dbModule = module {
// our dependencies
}
Teraz zdefiniujmy zależności dla: bazy danych
, FilmsDao
oraz klasy FilmsRepository
.
val dbModule = module {
// single instance of db
single {
Room.databaseBuilder(
androidApplication(),
FilmsDatabase::class.java,
DB_NAME
).build()
}
// factory instance of FilmsDao
factory<FilmsDao> {
get<FilmsDatabase>().filmDao()
}
// factory instance of FilmsRepository
factory<FilmsRepository> {
// "get" provided FilmsDao into FilmsRepository
FilmsRepository(filmsDao = get())
}
}
Teraz, skoro przenieśliśmy definiowanie naszej bazy danych do pliku modułu, możemy usunąć zbędny kod z klasy FilmsApplication
. Dzięki temu klasa zostanie oczyszczona, jak na poniższym przykładzie.
class FilmsApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
Moduł #2 – zeleżności ViewModel
Dla naszej klasy FilmsViewModel
, stwórzmy oddzielny plik z modułem o nazwie filmsModule
.
val filmsModule = module {
// instance of our FilmsViewModel
viewModel {
// "get" provided FilmsRepository into FilmsViewModel
FilmsViewModel(filmsRepository = get())
}
}
To wszystko 🙂 Nasze moduły oraz zależności są gotowe. Teraz nadszedł czas na ich wstrzyknięcie.
Wstrzykiwanie zależności
Teraz czas na najprostszą rzecz w tym artykule 🙂 Wstrzykiwanie zależności. Rozpoczniemy od klasy FilmsViewModel
.
FilmsViewModel – Wstrzyknięcie
By wstrzyknąć tą zależność wystarczy wywołać metodę viewModel()
.
class FilmsActivity : AppCompatActivity() {
private val viewModel: FilmsViewModel by viewModel()
Zauważ, że na tym etapie nie musimy już wstrzykiwać innych zależności. Wszystkie inne wstrzyknięcia nastąpiły już utworzonych modułach. Do klasy FilmsViewModel
zostało wstrzyknięte FilmsRepository
. Do FilmsRepository
zostało z kolei wstrzyknięte FilmsDao
, a do FilmsDao
została wstrzyknięta instancja naszej bazy danych
.
Uruchomienie Koina
W celu zarejestrowania i uruchomienia Koina, zastosujemy funkcję startKoin
.
class FilmsApplication : Application() {
override fun onCreate() {
super.onCreate()
initKoin()
}
private fun initKoin() {
startKoin {
androidLogger()
androidContext(this@FilmsApplication)
modules(provideModules())
}
}
private fun provideModules(): List<Module> =
listOf(
dbModule,
filmsModule
)
}
Wewnątrz niej wskazujemy, które moduły mają zostać użyte do tworzenia drzewa zależności. W naszej aplikacji będą to dbModule
oraz filmsModule
.
Zakończenie
I to już koniec! Wiesz już o co chodzi z Dependency Injection oraz z czym się je bibliotekę Koin. To na prawdę spoooro! Szczerze Ci gratuluję!
W kolejnym artykule spodziewaj się omówienia innej biblioteki stosowanej do Dependency injection – Daggera.
W razie jakichkolwiek pytań śmiało daj mi znać w komentarzu, na facebooku lub poprzez formularz kontaktowy
PS. wpadnij też po NIESPODZIANKĘ i zapisz się na mailing by nie przegapić maili merytorycznych oraz kolejnych postów.
Pozdrawiam,
Kamil Krzysztof.