iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
1
Mobile Development

Android TDD 測試驅動開發系列 第 22

Day22 - 依賴注入框架Koin

  • 分享至 

  • xImage
  •  

這篇要介紹Kotlin的輕量級依賴注入框架Koin。

為了在單元測試解除外部相依,我們使用了依賴注入的方式,例如在MVP的架構下,Activity需要在初始化Repository時注入Presenter。這將造成Activity的程式變得複雜。一般情況,Activity應該不需要知道Repository的。

實體書內容更豐富完整!
天瓏網路書店:Android TDD 測試驅動開發:從UnitTest、TDD到DevOps實踐

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。

https://ithelp.ithome.com.tw/upload/images/20191006/20111896vGAnzlcVhp.png

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)
        }
    }
}

https://ithelp.ithome.com.tw/upload/images/20191006/20111896SXg36eqAn3.png

新增完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 使用koin

回到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://insert-koin.io/

範例下載:
https://github.com/evanchen76/KoinSample
https://github.com/evanchen76/mvvmkoinsample

出版書:
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐

線上課程:
Android 動畫入門到進階
Android UI 進階實戰(Material Design Component)


上一篇
Day21 - Android MVVM 架構的單元測試
下一篇
Day23 - 使用Retrofit連接API的測試
系列文
Android TDD 測試驅動開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言