Android MVVM 架構(四)-使用 Koin
Dependency Injection 是一種優化程式架構的機制,如果要了解 DI(注入) 就需要先理解依賴反轉的概念,DI 是一種很方便的工具,它可以讓你不需要理會怎麼初始化一個物件,只需要透過簡單的方式就可以直接讓物件生成,即便物件後續有所調整,也不會影響我們既有的邏輯。
完整程式碼
本篇所用到的最終程式碼放在 GitHub 連結如下。
Koin 官方網站有很詳細的解釋跟說明,不過上面的教學可能需要再更新一下,如果您需要更新的資訊,可能需要到他的官方 GitHub 上參考。
Koin 是一套非常簡易操作的注入式工具,相較於 Google 官方出的 Dagger 來進行比較的話,個人是推薦使用 Koin 來操作,從網路上可以看到很多 Dagger 難以理解的說法,如果你有對於這兩套工具進行實作的話,相信會有很深刻的體會。
那我們就來把前面 Android MVVM 架構(三)-使用 RxJava 內的範例修改成 Koin 版本吧!
一開始導入 Koin library,你會發現它的導入有夠多:
Core features
// Koin for Kotlin
implementation "org.koin:koin-core:$koin_version"
// Koin extended & experimental features
implementation "org.koin:koin-core-ext:$koin_version"
// Koin for Unit tests
testImplementation "org.koin:koin-test:$koin_version"
// Koin for Java developers
implementation "org.koin:koin-java:$koin_version"
Android
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin Android Scope features
implementation "org.koin:koin-android-scope:$koin_version"
// Koin Android ViewModel features
implementation "org.koin:koin-android-viewmodel:$koin_version"
// Koin Android Experimental features
implementation "org.koin:koin-android-ext:$koin_version"
AndroidX
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"
Ktor
// Koin for Ktor Kotlin
implementation "org.koin:koin-ktor:$koin_version"
但實際上我們其實拿我們需要的函式庫即可,像我們這次的範例其實只是需要將 ViewModel 進行替換而已,因此不需要將所有的函式庫全部導入,以下就是這次範例所使用的。
def koin_version = '2.1.0-alpha-1'
// Koin for Kotlin
implementation "org.koin:koin-core:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
那目前 Koin 版本已經出到 2.1.0-alpha-1,所以將版號獨立出來方便以後升版不需要調整後面一堆 library,所以在前面定義一個 koin-version 的變數。
一開始建立一個 class 叫做 MyModule,這裡面是用來宣告定義我們的 ViewModel,告訴 Koin 之後我們初始化 ViewModel 就從這邊來進行提取。
val myModule = module {
viewModel { InfoViewModel(get()) }
}
val repoModule = module {
single { InfoRepository() }
}
這邊的 Repository 有兩種模式,一種是 single,另外一種是 factory,差別在於 single 就是 singleton 模式,會取得同一個物件,而另外一個 factory 就是工廠模式,它每次都會產生不同的物件。
從上面 Module 程式碼可以發現一個很方便的地方,當我們透過 InfoRepository 的實體後,InfoViewModel 必須傳入 Repository 的物件,所以 Koin 只需要呼叫 get() 就可以自動辨識將已經生成的物件傳入到 get() 這個參數。
這裡就宣告了我們的 ViewModel 以及 Repository,讓他們能夠透過程式自行生成,這樣一來我們就可以把一大串的 ViewModel 初始化進行簡化。
接著我們在 Mainifest 裡面增加一個 Application 的宣告,定義了 MyApplication,因為 Koin 需要做一些初始化的設定。
<application
android:name=".MyApplication"
//...
/>
接著到我們的 MyApplication 裡面的 OnCreate 最下方加入以下程式。
startKoin {
// Android context
androidContext(this@MyApplication)
// modules
val list = listOf(myModule, repoModule)
modules(list)
}
最後我們將原本的 ViewModel 宣告
private lateinit var infoViewModel: InfoViewModel
private lateinit var infoFactory: InfoFactory
private lateinit var infoRepository: InfoRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
infoRepository = InfoRepository()
infoFactory = InfoFactory(infoRepository)
infoViewModel = ViewModelProviders.of(this, infoFactory).get(InfoViewModel::class.java)
}
換成以下樣子。
private val infoViewModel by viewModel<InfoViewModel>()
天啊!就這麼簡單喔?沒錯你沒看錯,這樣 ViewModel 的實體就很簡單產生了,未來如果有需要改動 Repository 或者 ViewModel 的參數,就可以統一在 module 那邊進行調整。
這邊還可以使用 sharedViewModel 來進行宣告,差別在哪裡呢?
private val infoViewModel by sharedViewModel<InfoViewModel>()
差別在於,viewModel 是跟著 Fragment 頁面的生命週期,而 sharedViewModel 是跟著後面的 Activity 的生命週期,這樣一來,如果你有一些共通變數需要多個 Fragment 一起共用時(譬如說切到其他 Fragment 的時候,當畫面切換回來的 RecyclerView 位置),就可以透過 sharedViewModel 來進行宣告。
這樣就是一個簡單的 Koin 使用方法。
Android MVVM 架構(一)-使用 ViewModel、LiveData、Factory 以及 Repository