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)
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'
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()) }
- }
- }
- }
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)
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(isSingleton = false) { CoffeeMaker(get(), get()) }
provideFactory { CoffeeMaker(get(), get()) }
provide で name を指定することもできます。name を変えることで、同じ型を返す provide を複数定義できます。
- provide("Coffee") { CoffeeMaker(get(), get()) }
-
- provide("Coffee2") { CoffeeMaker(get(), get()) }
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()) }
- }
- }
- }
- }
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
provide { ElectricHeater(androidApplication) } bind Heater::class
3. アプリケーションクラスの onCreate() で startKoin() を呼ぶ
- class MyApplication : Application() {
-
- override fun onCreate() {
- super.onCreate()
-
- startKoin(this, listOf(DripCoffeeModule()))
- }
- }
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>()
- }
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) }
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>() }
- }
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()
- }
- }
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")
- }
class MainActivity : Activity() {
val maker by inject<CoffeeMaker>("Coffee")
}
Scope 名を指定してインスタンスを解放することができます。
- override fun onDestroy() {
- super.onDestroy()
- releaseContext("MainActivity")
- }
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>()
- }
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'
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)
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
- }
- }
@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
- }
@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()
val coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(DripCoffeeModule())
.build()
val maker = coffeeShop.maker()
assertThat(maker.heater).isNotNull()
assertThat(maker.pump).isNotNull()
0 件のコメント:
コメントを投稿