這篇要介紹Kotlin的輕量級依賴注入框架Koin。
為了在單元測試解除外部相依,我們使用了依賴注入的方式,例如在MVP的架構下,Activity需要在初始化Repository時注入Presenter。這將造成Activity的程式變得複雜。一般情況,Activity應該不需要知道Repository的。
class MainActivity : AppCompatActivity() {
private lateinit var presenter: Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val repository = Repository()
//注入repository
presenter = Presenter(repository)
val string = presenter.getString()
println("log:$string")
}
}
我們就來使用Koin依賴注入框架解決這個問題。
在build.gradle 加上
// 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"
// 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"
在這個範例裡有著MainActivity、Presenter、Repository。
Module 是一個容器,儲存了需要注入的物件實例化的方式,
以這個例子,我們想在Activity注入Presenter,那麼就需要在Module定義如何建立Presenter的Instance。
koinModule.kt
val koinModule = module {
factory {
Presenter(Repository())
}
}
接著需要初始化Koin,在Application.onCreate中使用startKoin
。新增一個Application,在這裡startKoin裡放入剛剛新增的module
class KoinSampleApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin{
androidLogger()
androidContext(this@KoinSampleApplication)
modules(koinModule)
}
}
}
新增完Application,AndroidManifest.xml 記得要加入。
<application
android:name=".KoinSampleApplication"
/>
回到Activity,開始使用koin的方式注入。在 Presenter 後面加上 get
。就會產生Presenter的Instance。這樣Activity的程式碼就乾淨許多了。
在要實現注入的地方用get取得實例
private val presenter: Presenter = get()
class MainActivity : AppCompatActivity() {
//這裡不需要再注入Repository了
private val presenter: Presenter = get()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val string = presenter.getString()
println("log:$string")
}
}
也可以用 by inject()
做延遲注入。
private val presenter: Presenter by inject()
回到MVVM上一個範例。我們就來看要怎麼在ViewModel使用Koin。
原作法在建立ViewModel時,為了要達到依賴注入。在Activity會需要注入ProductAPI及ProductRepository
val productAPI = ProductAPI()
val productRepository = ProductRepository(productAPI)
productViewModel =
ViewModelProviders.of(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
新增AppModule.kt,在這裡提供ProductRepository
val appModule = module {
viewModel {
val productAPI = ProductAPI()
val productRepository = ProductRepository(productAPI)
ProductViewModel(productRepository)
}
}
新增一個Application,在onCreate裡,startKoin
裡放入剛剛新增的appModule
。
class MVVMKoinApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin { modules(listOf(appModule)) }
}
}
這邊要記得AndroidManifest要把新增的Application加入
<application
android:name=".MVVMKoinApplication”
/>
最後回到Activity,Koin支援viewModel的注入方式。只要改加上 by viewModel()
就會注入了。
private val productViewModel: ProductViewModel by viewModel(),這樣Activity就不會需要在這裡注入ProductRepository了。
class ProductActivity : AppCompatActivity() {
private val productId = "pixel3"
//加上by viewModel
private val productViewModel: ProductViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
val dataBinding = DataBindingUtil.setContentView<ActivityProductBinding>(this, R.layout.activity_product)
//改用Koin,註解以下
// val productAPI = ProductAPI()
// val productRepository = ProductRepository(productAPI)
// productViewModel =
// ViewModelProviders.of(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
dataBinding.productViewModel = productViewModel
dataBinding.lifecycleOwner = this
}
這樣就完成使用koin注入。原本的這幾行,也可以刪掉了。
// val productAPI = ProductAPI()
// val productRepository = ProductRepository(productAPI)
// productViewModel =
// ViewModelProviders.of(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
依賴注入框架,除了koin另一個比較有名的就是dagger了。但Koin在使用上比起dagger簡單。如果你正在使用dagger,也可以考慮移轉到koin。
範例下載:
https://github.com/evanchen76/KoinSample
https://github.com/evanchen76/mvvmkoinsample
出版書:
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐
線上課程:
Android 動畫入門到進階
Android UI 進階實戰(Material Design Component)