Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

AndroidのWorkManagerでDIする

こんにちは!Sansan事業部 プロダクト開発部のふるしんです。
私は大阪のオフィスでSansanプロダクトのAndroidアプリの開発に従事しています。
play.google.com


WorkManagerを使うと、アプリの起動状態が変化しても実行してくれるような、延期可能な非同期タスクのスケジューリングを簡単に設定できるようになります。
公式ドキュメントはこちらです。
developer.android.com


WorkManagerは非常に便利ですが、DI(Dependency Injection)するには少し工夫が必要です。
今回はDaggerを使った方法をご紹介します。

今回目指すゴール

次のようなクラスを作れることを目指します。

class HelloWorldWorker(context: Context, params: WorkerParameters) : DaggerWorker(context, params) {
    @Inject
    internal lateinit var hello: Hello

    override fun doWorkAfterInject(): Result {
        // do something
        // ...
    }
}


それでは進めていきます。

必要なライブラリを導入する

app/build.gradle に必要なものを追加します。

dependencies {
    // Dagger
    implementation "com.google.dagger:dagger:2.27.0"
    implementation "com.google.dagger:dagger-android:2.27.0"
    implementation "com.google.dagger:dagger-android-support:2.27.0"
    kapt "com.google.dagger:dagger-compiler:2.27.0"
    kapt "com.google.dagger:dagger-android-processor:2.27.0"

    // WorkManager
    implementation 'androidx.work:work-runtime-ktx:2.3.4'

DIするためのInterfaceを用意する

DIする場合、 @Inject constructor() を使うパターンとメンバー変数に @Injectアノテーションを付けるパターンとあります。
今回はメンバー変数に@Injectアノテーションを付ける方法でやってみます。

まずはInterfaceを用意します。

interface HasWorkerInjector {
    fun workerInjector(): AndroidInjector<Worker>
}

このInterfaceを活用してDIしていきます。
ちなみにInterfaceの命名は本家のDaggerに合わせています。
Interface名の頭にHasが付いていることに多少の違和感はありますが、本家がこのようにHasAndroidInjectorとなっています。
本家に合わせることで「あ、Daggerはこんな命名規則なんだな」と認識できるようにするためだと理解してください。

github.com

それでは実際にDIするためのクラスを用意します。

abstract class DaggerWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        (applicationContext as HasWorkerInjector).workerInjector().inject(this)
        return doWorkAfterInject()
    }

    abstract fun doWorkAfterInject(): Result
}

実際に処理を実行したい場合はこのDaggerWorkerクラスを継承します。

WorkManagerのWorkerでは実行するとdoWorkメソッドが実行されます。
doWorkのタイミングでDIすることでメンバー変数へ注入できるようにしました。

DIしたいWorkerクラスを用意する

冒頭に述べたクラスを用意しておきます。

class HelloWorldWorker(context: Context, params: WorkerParameters) : DaggerWorker(context, params) {
    @Inject
    internal lateinit var hello: Hello

    override fun doWorkAfterInject(): Result {
        // do something
        // ...
    }
}

先ほど作成したDaggerWorkerを継承し、メンバー変数に@Injectアノテーションを付加することでInjectしています。
実際に実行した際に行いたい処理はdoWorkAfterInjectメソッドに記載します。

DIする設定をModuleに追加する

あとは通常のDaggerの使い方に沿って、ModuleとComponentに情報を追加していきます。

Worker用のComponentを作成する

まずはComponentを用意します。
今回はSubComponentとして用意しました。

@Subcomponent(modules = [
    AndroidSupportInjectionModule::class
])
interface HelloWorldWorkerComponent : AndroidInjector<HelloWorldWorker> {
    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<HelloWorldWorker>
}

Worker用のModuleを作成する

用意したComponentをModuleに登録します。

@Module(subcomponents = [HelloWorldWorkerComponent::class])
abstract class WorkerBindingModule {
    @Binds
    @IntoMap
    @ClassKey(HelloWorldWorker::class)
    abstract fun bindHelloWorldWorkerComponentInjectorFactory(builder: HelloWorldWorkerComponent.Factory): AndroidInjector.Factory<*>
}

Worker用のSubComponentをComponentへ登録する

作成したWorker用のSubComponentをComponentへ登録します。
@Componentアノテーションへ、他にも用意しているModuleがあれば一緒に配列として定義します。

@Component(modules = [
    AndroidSupportInjectionModule::class,
    // 他のModule群…
    WorkerBindingModule::class
])
@Singleton
interface MyComponent : AndroidInjector<MyApplication> { }

MyApplicationへHasWorkerInjectorを登録する

最後に、HasWorkerInjectorをMyApplicationへ登録しておきます。

class MyApplication : DaggerApplication(), HasWorkerInjector {
    @Inject
    internal lateinit var workerInjector: DispatchingAndroidInjector<Worker>

    override fun workerInjector(): AndroidInjector<Worker> {
        return workerInjector
    }

MyApplicationはAndroidManifestで登録することを忘れないようにしましょう!

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.sansan">

    <application
        android:name=".MyApplication"
        />

これで全て完了です!

まとめ

構成が複雑になってしまわないかな?という不安は少しありましたが、思っていたよりも簡単な構成とできました!

Googleは昨年10月に行われたAndroid Dev Summitにて以下のように述べ、Daggerの利用を推奨しています。

Actually, we want you to use dagger.
So, It's our recommendation tools.
And we think it's the best framework out there to do dependency injection because of its correctness, performance and scalabillity.

www.youtube.com

我々もDaggerを積極的に活用していこうと考えています。

最後に

Sansanではたくさんの仲間を募集しています!
特に大阪のAndroidな人!来てくれ!友達からお願いします!
jp.corp-sansan.com
hrmos.co



buildersbox.corp-sansan.com
buildersbox.corp-sansan.com

© Sansan, Inc.