Koinを使ってWorkManagerのWorkerを生成する

2021/01/01に公開

Koin2.2 から、org.koin:koin-androidx-workmanager が追加され、module 内で ListenableWorker を継承した Worker を定義することが可能になりました。
試しに利用してみたところ、現時点で利用できる機能だけでは悩ましい箇所もありました。
今回は、Koin を利用した Worker 生成方法と、悩ましかった個所の解消方法をライブラリの実装含めて解説します。

対象読者

  • Koin について、module の定義など基本的な利用方法にある程度理解がある
  • AndroidX の WorkManager についてある程度理解がある

koin-androidx-workmanager について

前述したとおり、koin-androidx-workmanager を利用することで module 内に Listenableworker を継承した Worker を定義することが可能になります。

WorkManager で動かす Worker は、WorkManager のカスタマイズを行わない限りは ContextWorkerParameters のみを引数に持つコンストラクタを用意する必要がありますが、Koin に Worker の生成を任せることによってこの制約がなくなります。
よって、 Worker に対してコンストラクタで依存性の注入が実行可能となります。
Koin を利用しなくとも同様の実装が可能ですが、利用することによってより手軽に実現できるのが利点のひとつですね。

すでに Koin を使って Worker を実装しており、何かしらのクラスをフィールドインジェクションしている場合は、この機能を利用することで Worker 内部の処理がより Koin に依存しない形で実装できるようになります。

フィールドインジェクションを行っている場合は、ユニットテストを書くときも Koin を意識してテストを書く必要があるので、コンストラクタインジェクションの形に書き換えられるのはテストしやすいクラスを実現する点でもよいと思っています。

利用方法

以下の 4 点を対応する必要があります。

  • org.koin:koin-androidx-workmanager を依存関係に追加する
  • AndroidManifest に WorkManager 関連の provider を削除する定義を追加する
  • startKoin で workManagerFactory() を呼び出す
  • module に worker の定義を追加する

具体的な対応方法については、ドキュメントがあるのでそちらを参照するのがよいです。
後述する問題がいくつかありますが、Koin の設定ができていれば導入は簡単です。

https://doc.insert-koin.io/#/koin-android/workmanager?id=workmanager-dsl

Worker を Koin で生成する仕組み

WorkManager は、Worker を生成する WorkerFactory を内部に保有しており、スケジューリングされたタイミングで WorkerFactory から Worker を生成して実行します。
また、WorkerFactory は、WorkManager の初期化を行うタイミングで任意の WorkerFactory を設定できます。
org.koin:koin-androidx-workmanager では、Koin の module 定義を呼び出すことが可能な WorkerFactory を WorkManager に設定することで、Koin を利用した Worker の生成を実現しています。
この WorkerFactory を設定して WorkManager の初期化を実行しているのが workManagerFactory() です。

また、WorkManager のカスタマイズを行う場合は、WorkManager のデフォルトの初期化処理を無効化する必要があります。
https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration#remove-default

よって、Koin のドキュメントに書いてある WorkManager の初期化を行う provider を AndroidManifest から remove する手順が必要となります。

悩ましい点

workManagerFactory() を利用して WorkManager を設定する場合は、現状では外部から WorkManager の設定を追加できず、 Koin 向けのカスタマイズのみ実行されてしまいます。
そのため、WorkManager のカスタマイズを他にも行いたい場合は、workManagerFactory() を利用しないで対応する必要があります。

workManagerFactory() を利用しない場合

Application を継承したクラスに Configuration.Provider を実装して、getWorkManagerConfiguration() で Koin の module を参照する WorkerFactory を設定することで対応できます。
以下のコードで利用している KoinWorkerFactory が、Koin の module を参照する WorkerFactory になります。

class SampleApplication : Application(), Configuration.Provider {
    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@SampleApplication)
            modules(sampleModule)
        }
    }

    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder().also { builder ->
            // Koin の module を参照する WorkerFactory を設定
            val workerFactory =
                DelegatingWorkerFactory().also { it.addFactory(KoinWorkerFactory()) }
            builder.setWorkerFactory(workerFactory)

            // 他の WorkManager カスタマイズ設定  
            builder.setJobSchedulerJobIdRange(1, 10)
        }.build()
    }
}

また、上記の KoinWorkerFactory を設定する処理は workManagerFactory() 関数内で実行しているものとほぼ同じです。
https://github.com/InsertKoinIO/koin/blob/2.2.0/koin-projects/koin-androidx-workmanager/src/main/java/org/koin/androidx/workmanager/koin/KoinApplicationExt.kt#L36

補足

Injection Parameters を get() で解決できない場合がある

Koin 2.2.0 で、factory,single などの DSL で Injection Parametersget() で解決できる機能が追加されています。
しかし、worker DSL を利用している場合は get() による引数の取得が失敗するようです。
この記事を書いている時点の最新版である Koin 2.2.2 でも問題が発生していることを確認しました。

worker DSL の実装を確認したところ、factory,single と比べて特殊なことをしているわけでもなさそうなので、Injection Parameters を get() で解決する処理自体が不安定な可能性もありそうです。
Koin 2.2.2 時点で worker DSL を使う場合は、以下のような実装にしたほうが安定して動きます。

    worker { (params: WorkerParameters) ->
        SampleWorker(get(), params, get())
    }

サンプルコード

Koin のサンプルコードをまとめているリポジトリに WorkManager の実装も追加しておきました。
https://github.com/djyugg/Koin2.2AndroidSample/commit/f38ccb515c0e1e164ee0fb5f0a75e61e95844ce3

Discussion