Dependency Injection – Koin – Projekt

Artykuł poświęcony jest bibliotece Koin do wstrzykiwania zależności. W tej publikacji zastosujesz tą bibliotekę w realnym projekcie.

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:

  1. Dodać zależność biblioteki do pliku build.gradle: implementation „org.koin:koin-androidx-viewmodel: 2.0.1”
  2. Stworzyć moduły, w których zadeklarujemy niezbędne zależności
  3. Wstrzyknąć zależności w odpowiednie miejsca
  4. 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ć:

  1. Bazę danych
  2. FilmsDao
  3. FilmsRepository
  4. 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 danychFilmsDao 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.

  1. GitHub – Sprzed wprowadzenia DI >>
  2. GitHub – Po wprowadzeniu DI >>
  3. Link do Niespodzianki