Koinを使って依存解決(DI)する
こんにちは、tkyです。
今回は
今までDI(Dependency Injection)ライブラリはDagger2だけしか使ったことがなかったのですが、他のDIライブラリも使ってみたくて Koin を使ってみました。
公式見ながら作業しましたが、想像以上に簡単にDIできたのでびっくり。。。
以下のような2つの数字を足し算するだけのアプリでお試し実装してみました。

作成したものはGitHubに置いてありますのでご確認ください。
アプリ自体はMVVMを採用し、DataBindingを活用していますが、Koinに特化して記載するため、Bindingについては触れません。 github.com
環境情報
- Android Studio 3.5
- Kotlin 1.3.50
- Koin 2.0.1
Koin
Kotlin用の軽量な依存注入フレームワーク(日本語訳)です。2019/09/23時点の最新は v2.0.1 となっています。
使い方
Koin自体は超らくちんで、App にKoin使う宣言して、モジュールの宣言をするだけです。
app.gradle
implementation 'org.koin:koin-android:2.0.1'
implementation 'org.koin:koin-android-scope:2.0.1'
implementation 'org.koin:koin-android-viewmodel:2.0.1'
App.kt
import android.app.Application import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin class App : Application() { override fun onCreate() { super.onCreate() // ↓これだけ!DSLでかけるのがいいですね! startKoin { androidContext(this@App) modules(myModule) } } }
modules() に依存を解決したいモジュールの宣言をしていきます。
今回は KoinInjector.kt ファイルを作って、その中に変数を定義して見ました。
Calculator 、 CalculateService 、 MainViewModel については後述します。
import org.koin.android.viewmodel.dsl.viewModel import org.koin.dsl.module // ちゃんと定数とわかるような命名規則のほうが良いです。 val myModule = module { single { Calculator() } single { CalculateService(get()) } viewModel { MainViewModel(get()) } // ViewModel用のモジュール宣言 }
後出しになりますが、サンプルアプリはクラス間で以下のような依存関係にあります。

Calculator クラスは他のクラスに依存しない計算ロジックのクラスです。
myModule には single { Calculator() } のようにして依存解決します。
class Calculator { fun sum(a: Int, b: Int): Int = a + b }
CalcurateService クラスは計算に関する処理をまとめるためのサービスクラスのような扱いで一枚かませました。DIしたかったし😅
実際、 CalcurateService はRepository(DataSource)層、 Calculator は Dao層のような関係で理解すると良いかもしれません。
Calculator に依存していますが、Calculator のインスタンスはコンストラクタから注入します。
myModule には single { CalculateService(get()) } のようにして依存解決します。
class CalculateService(private val calculator: Calculator) { fun sum(a: Int, b: Int): Int = calculator.sum(a, b) }
MainViewModel は CalculateService に依存しています。
これもコンストラクタからインスタンスを注入します。
myModule には viewModel { MainViewModel(get()) } のようにして依存解決します。
import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class MainViewModel(private val calculateService: CalculateService) : ViewModel() { private var _result: MutableLiveData<String> = MutableLiveData() val result: LiveData<String> get() = _result fun calculate(a: String?, b: String?) { val numA = if (a.isNullOrEmpty()) 0 else a.toInt() val numB = if (b.isNullOrEmpty()) 0 else b.toInt() val result = calculateService.sum(numA, numB).toString() _result.value = result } }
MainActivity は MainViewModel に依存しています。
MainActivity はAndroidクラスにつき、コンストラクタから注入できません。
その代わりKoinが遅延初期化の仕組みを用意してくれています。
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity // import org.koin.android.ext.android.get import org.koin.android.ext.android.inject class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 関数内でもこのように依存注入できます。 // val viewModel: MainViewModel = get() } }
これで依存解決されたMainViewModelのインスタンスが出来上がりです!
まとめ
コードを貼り付けただけでしたが、やることは
- App.ktに以下を書く
startKoin {
androidContext(this@App)
modules(myModule)
}
- モジュール(myModule)の依存を解決する
val myModule = module { single { Calculator() } single { CalculateService(get()) } viewModel { MainViewModel(get()) } }
- ActivityでDIする
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { ・・・略・・・ } }
たった3つ!って思うとやってみたくなりません?
公式にもGet Startedがあるので見てみてください。