2017年11月23日木曜日

KOIN 使ってみた

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

使い方

interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon(private val heater: Heater) : Pump class CoffeeMaker(val heater:Heater, val pump:Pump)

1. dependency

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

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

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

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

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

4. inject

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

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


おまけ、Daggerの場合

kapt 'com.google.dagger:dagger-compiler:2.11' implementation 'com.google.dagger:dagger:2.11' interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon @Inject constructor(private val heater: Heater) : Pump class CoffeeMaker @Inject constructor(val heater: Heater, val pump: Pump) @Module class DripCoffeeModule { @Provides fun provideHeater(): Heater { return ElectricHeater() } @Provides fun providePump(pump: Thermosiphon): Pump { return pump } } @Component(modules = arrayOf(DripCoffeeModule::class)) interface CoffeeShop { fun maker(): CoffeeMaker } val coffeeShop = DaggerCoffeeShop.builder() .dripCoffeeModule(DripCoffeeModule()) .build() val maker = coffeeShop.maker() assertThat(maker.heater).isNotNull() assertThat(maker.pump).isNotNull()


0 件のコメント:

コメントを投稿