2017年11月23日木曜日

KOIN 使ってみた

KOIN は Android 向けのシンプルな Dependency Injection フレームワークです。Kotlin の機能を使って DI を実現しています(proxy/CGLib なし、コード生成なし、introspection(リフレクションとかバイドコードいじりとか)なし)。

使い方

  1. interface Heater  
  2.   
  3. interface Pump  
  4.   
  5. class ElectricHeater : Heater  
  6.   
  7. class Thermosiphon(private val heater: Heater) : Pump  
  8.   
  9. class CoffeeMaker(val heater:Heater, val pump:Pump)  

1. dependency

  1. implementation 'org.koin:koin-android:0.6.0'  
  2. testImplementation 'org.koin:koin-test:0.6.0'  

2. AndroidModule を継承したクラスを用意し、context() メソッドを実装する

この Context は Android の Context ではなくて org.koin.dsl.context.Context です。 Context の applicationContext() を使って構成します。
  1. class DripCoffeeModule : AndroidModule() {  
  2.   
  3.     override fun context(): Context {  
  4.         return applicationContext {  
  5.             provide { ElectricHeater() } bind Heater::class  
  6.   
  7.             provide { Thermosiphon(get()) } bind Pump::class  
  8.   
  9.             provide { CoffeeMaker(get(), get()) }  
  10.         }  
  11.     }  
  12. }  
applicationContext() は name として Scope.ROOT を指定した Context を生成します。
  1. fun applicationContext(init: Context.() -> Unit) = Context(Scope.ROOT, koinContext).apply(init)  
provide はデフォルトでは singleton になります。singleton にしない場合は isSingleton に false を指定するか、provideFactory を使います。
  1. provide(isSingleton = false) { CoffeeMaker(get(), get()) }  
  2.   
  3. provideFactory { CoffeeMaker(get(), get()) }  
provide で name を指定することもできます。name を変えることで、同じ型を返す provide を複数定義できます。
  1. provide("Coffee") { CoffeeMaker(get(), get()) }  
  2.   
  3. provide("Coffee2") { CoffeeMaker(get(), get()) }  
context() で sub context を作ることができます。Context には Scope 名を指定することができます。
  1. class DripCoffeeModule : AndroidModule() {  
  2.   
  3.     override fun context(): Context {  
  4.         return applicationContext {  
  5.             context("MainActivity") {  
  6.                 provide { ElectricHeater() } bind Heater::class  
  7.   
  8.                 provide { Thermosiphon(get()) } bind Pump::class  
  9.   
  10.                 provide { CoffeeMaker(get(), get()) }  
  11.             }  
  12.         }  
  13.     }  
  14. }  
provide するインスタンスを生成するときに Android の Context が必要な場合、androidApplication で Application インスタンスを取得することができます。
  1. provide { ElectricHeater(androidApplication) } bind Heater::class  

3. アプリケーションクラスの onCreate() で startKoin() を呼ぶ

  1. class MyApplication : Application() {  
  2.   
  3.     override fun onCreate() {  
  4.         super.onCreate()  
  5.   
  6.         startKoin(this, listOf(DripCoffeeModule()))  
  7.     }  
  8. }  
startKoin() は android.app.Application の拡張関数として定義されています。

4. inject

  1. class MainActivity : Activity() {  
  2.   
  3.     val maker by inject<CoffeeMaker>()  
  4. }  
inject()
  1. inline fun <reified T> ComponentCallbacks.inject(name: String = "") = lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name) }  
なので以下と同じ、つまり lazy です。
  1. class MainActivity : Activity() {  
  2.   
  3.     val maker by lazy { (StandAloneContext.koinContext as KoinContext).get<CoffeeMaker>() }  
  4. }  
lazy が嫌なら onCreate() で (StandAloneContext.koinContext as KoinContext).get() を自分で呼んで代入すればできますが、全部自動で一括でとなるとやはりアノテーションなどが必要ですね。
  1. class MainActivity : Activity() {  
  2.   
  3.     private lateinit var maker: CoffeeMaker  
  4.   
  5.     override fun onCreate(savedInstanceState: Bundle?) {  
  6.         super.onCreate(savedInstanceState)  
  7.   
  8.         maker = (StandAloneContext.koinContext as KoinContext).get()  
  9.     }  
  10. }  


provide で name を指定した場合、inject にその name を指定して取得できます。
  1. class MainActivity : Activity() {  
  2.   
  3.     val maker by inject<CoffeeMaker>("Coffee")  
  4. }  
Scope 名を指定してインスタンスを解放することができます。
  1. override fun onDestroy() {  
  2.     super.onDestroy()  
  3.     releaseContext("MainActivity")  
  4. }  
ContextAwareActivity を継承して Scope 名を指定すると同じことができます。ContextDropMethod を指定しないときのデフォルトは ContextDropMethod.onPause(onPause() で releaseContext() される)です。
  1. class TestActivity : ContextAwareActivity("MainActivity", ContextDropMethod.OnDestroy) {  
  2.   
  3.     val maker by inject<CoffeeMaker>()  
  4. }  



おまけ、Daggerの場合

  1. kapt 'com.google.dagger:dagger-compiler:2.11'  
  2. implementation 'com.google.dagger:dagger:2.11'  
  1. interface Heater  
  2.   
  3. interface Pump  
  4.   
  5. class ElectricHeater : Heater  
  6.   
  7. class Thermosiphon @Inject constructor(private val heater: Heater) : Pump  
  8.   
  9. class CoffeeMaker @Inject constructor(val heater: Heater, val pump: Pump)  
  1. @Module  
  2. class DripCoffeeModule {  
  3.   
  4.     @Provides  
  5.     fun provideHeater(): Heater {  
  6.         return ElectricHeater()  
  7.     }  
  8.   
  9.     @Provides  
  10.     fun providePump(pump: Thermosiphon): Pump {  
  11.         return pump  
  12.     }  
  13. }  
  1. @Component(modules = arrayOf(DripCoffeeModule::class))  
  2. interface CoffeeShop {  
  3.     fun maker(): CoffeeMaker  
  4. }  
  1. val coffeeShop = DaggerCoffeeShop.builder()  
  2.         .dripCoffeeModule(DripCoffeeModule())  
  3.         .build()  
  4.   
  5. val maker = coffeeShop.maker()  
  6.   
  7. assertThat(maker.heater).isNotNull()  
  8. assertThat(maker.pump).isNotNull()  



0 件のコメント:

コメントを投稿