【Android】DIライブラリKoinの勧め


モバイルエンジニアとして、CBcloudに参画したbigbackboomです。メインでAndroidアプリの開発を行なっています。
現在、弊社のAndroidアプリはモバイルエンジニアがいなかったこともあり、独自のアーキテクチャを採用していて非常に開発しにくいものになっています。その改善のためにリファクタリングでMVVMアーキテクチャを導入したのですが、DIライブラリにはKoinを採用しました。今回はなぜ主流ライブラリのDaggerではなく、新参のKoinを採用したのかを中心に書いていきます!

そもそもDependency Injectionって?

Inversion of Control Principle"(制御の反転の原則)を実現するための手法です。Dependency Inject(以下DI)を使うことでクラスの依存を全て外部から注入し、クラス同士の結合を弱めることができる。そのためテストのしやすい構造と、変更強い構造を同時に実現することができます。

Koinとは?

Kotlin向けの軽量DIライブラリ。Reflection、Proxy、コードの自動生成も行わないので、ビルド時間の増加・ランタイムのオーバーヘッドなしでDIを実現することができます。

なぜKoinなのか(why not stub with Dagger?)

Daggerを使ってAndroidのプロジェクトを0→1で開発したことのあるエンジニアであれば分かることですが、Daggerの設定は非常に分かりにくいです。設定が難しいだけでなく、エラーが起きても何が原因なのか特定し辛く、ビルドが失敗しても原因箇所が示されないので、どうすれば良いのか分からず右往左往してしまいます。

それと比べて、Koinは設定が非常に簡単で優秀です。コードの自動生成は一切行わないので、設定の成否問わずビルドを通すことができます。エラーの発生はランタイムでのみ起こるので、Daggerと比べると非常に問題の特定がしやすいです。

実装(Let's insert a Koin)

準備

build.gradleファイルに以下の依存性を書き加えます。

implementation "org.koin:koin-android:2.0.1"
implementation "org.koin:koin-androidx-scope:2.0.1"
implementation "org.koin:koin-androidx-viewmodel:2.0.1"

簡単なアプリを作る

ボタンを押すとカウント表示がされる簡単なアプリ

// MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val viewModel: MainViewModel by currentScope.inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.button.setOnClickListener{
            viewModel.onButtonClicked()
        }
        binding.lifecycleOwner = this
        binding.viewModel = viewModel
    }
}
// MainViewModel
class MainViewModel(private val numberCounter: NumberCounterInterface): ViewModel() {

    var counterLiveData: MutableLiveData<String> = MutableLiveData("0")

    fun onButtonClicked(){
        numberCounter.countUp()
        counterLiveData.postValue(numberCounter.count.toString())
    }
}
// NumberCounterInterface.kt
interface NumberCounterInterface {
    var count: Int
    fun countUp()
}

// NumberCounter.kt
class NumberCounter: NumberCounterInterface {

    override var count = 0

    override fun countUp() {
        count++
    }
}

Koinの設定


クラスのスコープを定義したモジュール変数を作成する変数を
```kotlin
val viewModelModule = module {

    single {
        NumberCounter() as NumberCounterInterface
    }
}
val activityModule = module {
    // MainActivityのスコープ内でMainViewModelを使うことを定義する
    // MainViewModelの依存はget()と記載することで別のモジュールが解決してくれる
    scope(named<MainActivity>()) {
        scoped { MainViewModel(get()) }
    }
}

アプリが絶対に通る処理に、Koinの初期化処理を書く。そこでモジュールの登録を行う

// CustomApplication.kt
class CustomApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            // Koin Android ロガー
            androidLogger()
            //Android Contextを設定する
            androidContext(this@CustomApplication)
            // モジュールを登録する
            modules(listOf(activityModule, viewModelModule))
        }
    }
}

DONE!!!!!

これで、currentScope.inject()が呼ばれた時点で、一連のインジェクト処理が行われます。

結論

KoinはDAggerよりも手軽に、簡単な設定でDIを実現できる素晴らしいライブラリです。簡単な分チームでのナレッジの共有もしやすいので、Android強者が集まるモバイルチームがない場合は、Koinを採用する方がいいかもしれないです。

サンプルコードはこちらです