【Android】WorkManagerでHiltを使う

# まえがき
これはWorkManagerを使って，1日に1度のバックグラウンドタスクをスケジューリングするためのアプリ作ることを目的とします．

今回は，24時間分のユーザのアプリ使用履歴を取得してテキストとして記録するという機能を，1日に1度発火させるようにします．

あくまでも備忘録なモチベーションなのであしからず．

# WorkManagerとは
一応，WorkManagerについて触れておきます．

以下公式ドキュメント
>WorkManager は、永続処理のための推奨ソリューションです。アプリの再起動やシステムの再起動後もスケジュール設定が維持された場合、処理は永続します。ほとんどのバックグラウンド処理は永続処理で実現することが最善であるため、WorkManager が、バックグラウンド処理に推奨される主要な API です。

また，AlarmManagerも似たようなことができますが，これは絶亭に正確な時刻に発火が必要なものに対して使うものであり，WorkManagerに比べるとバッテリーに優しくないそうです．
また，デバイスがオンラインの場合，電源に接続されている場合のみ実行など条件をつけることも可能です．

今回は，正確性よりも，ひっそりバックグラウンで動いてくれることを望むのでWorkManagerで実装します．

# 早速実装
WorkManagerを実現させるには大まかに以下が必要です．
1. WorkManagerを使って動かしたいなにか
1. Worker
1. Workを登録する処理

WorkManagerを使って動かしたいものは，準備してあるものとします．
自身の場合は以下です
```Kotlin
val usageStats = GetUsageStats(context).getUsageStats()
usageRepository.appendUsage(fileName, usageStats)
```
`getUsageStats()`を使って`List<UsageStats>`を取得しています（使用履歴のリスト）．
こちらを`UsageRepository`を通して，`appendUsage()`を呼び出し，端末内に保存しています．

また，今回はDIを通すために，Applicatoin()を継承したクラスも使用します．

## 依存関係の追加
```Gradle
    //workmanager
    def work_version = "2.7.1"
    implementation "androidx.work:work-runtime:$work_version"
    implementation "androidx.work:work-runtime-ktx:$work_version"

    //Hilt
    implementation 'com.google.dagger:hilt-android:2.44'
    kapt 'com.google.dagger:hilt-compiler:2.43.2'
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'

    //worker with hilt
    implementation 'androidx.hilt:hilt-work:1.0.0'
    // When using Kotlin.
    kapt 'androidx.hilt:hilt-compiler:1.0.0'
    // When using Java.
    annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0'

    // For instrumentation tests
    androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
    kaptAndroidTest 'com.google.dagger:hilt-compiler:2.43.2'
    // For local unit tests
    testImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
    kaptTest 'com.google.dagger:hilt-compiler:2.43.2'
```

HiltとWorkerについての依存関係を載せておきます．

## Workerをセットする
Hiltを使っているため，普通のWorkerにちょっと記載が増えてます．
Corutineが使われる，`CorutineWorker`を継承したWorkerを作成します．
 
以下の`doWork()`のブロック内に期待する処理を記述することでバックグラウンドでその処理が実行されます．

今回はHiltを使ってDIを行うため，　`＠HiltWorker`というアノテーションを付与します．
また，DIを通してあるRepositoryを使用するために`＠AssistedInject constructor()`を使います．

もともと，CorutineWorkerで必要な引数には，`＠Assited`を付与します．

これらアノテーションを用いることで，本来呼び出すことのできないRepositoryを呼び出すことができます．
これは，実質的には`WorkerFactory`を作り直すことで実現しています．
WorkerFactoryの宣言は`Application`を継承したクラスで行います．後で記載します．


```Kotlin
@HiltWorker
class GetUsageWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted params: WorkerParameters,
    private val usageRepository: UsageRepository,
): CoroutineWorker(appContext, params) {
    companion object {
        const val WORK_NAME = "com.example.accumulateusage.works.GetUsageWorker"
    }

    private val fileName = "data.txt"
    private val context = appContext

    //ここに何をバックグラウンドで実行するのかを記述する．
    override suspend fun doWork(): Result {
        try {
            Log.i("Worker","Work request for sync is run")
            val usageStats = GetUsageStats(context).getUsageStats()
            usageRepository.appendUsage(fileName, usageStats)
        }catch (e: Exception){
            Log.i("Worker","Error: $e")
        }
        return Result.success()
    }
}
```
上記のワーカーをどこかからセットすることでWorkとして登録できます．

## Workの登録
今回は画面上のボタンを押すことでWorkerを登録します
登録処理自体はViewModelに記載されています．
Screen側のボタンのタップで処理を呼び出しますが，今回はUIの方のコードは省略します．

`PeriodicWorkRequestBuilder<GetUsageWorker>(24, TimeUnit.HOURS)`の部分で24時間おきに動いてくれるWorkerを登録しています．

```kotlin
class MainViewModel @Inject constructor(
    private val usageRepository: UsageRepository
): ViewModel(){
    fun setWorkManager(context: Context){
        val request = PeriodicWorkRequestBuilder<GetUsageWorker>(24, TimeUnit.HOURS)
            .build()
        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            GetUsageWorker.WORK_NAME,
            ExistingPeriodicWorkPolicy.KEEP,
            request
        )
    }
}
```
## Application ClassにFactoryを作る
今回はDIを行うために，Workerの引数がデフォルトと違います．
普段は自動でインスタンスを作ってくれますが，今回の場合はWorkerを作る手順を記述する必要があります．

そのため，WorkerFactoryを定義したりします．
```kotlin
@HiltAndroidApp
class AccumulateUsageApp: Application(), Configuration.Provider {

    //Hilt用のWorkerFactory
    @Inject lateinit var workerFactory: HiltWorkerFactory

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: AccumulateUsageApp private set
    }

    WorkerFactoryをセットする．
    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
}
```

## Manifestを編集する．
上記のようなWorkManagerの設定を行うと，マニフェストを編集しなければWokerが正常にセットできなくなります．
理由としては，WorkManagerがandroidx.startupのイニシャライザを使用するためです．

Manifestに，以下の記述をすることでデフォルトのイニシャライザを削除できます．

アプリでアプリの起動をしない場合．
```kotlin
 <!-- If you want to disable android.startup completely. -->
 <provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove">
 </provider>
```
アプリでアプリの起動を行う場合
```kotlin
 <provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- If you are using androidx.startup to initialize other components -->
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
 </provider>
```
こちらでコードの記述は以上になります．
すべてのコードは以下に乗せてあります．
https://github.com/M0710Fa/AccumulateUsage

# まとめ
WorkMangerでHiltを使うときに困らないように残しておきました．

動作確認などは，Logを見たり，AppInspectionのBackground Task Inspectorなどを使うと良いと思います．

自分自身勉強中の身で，誤りもあるかもわかりませんが，公式情報などと並行して参考にしていただければ幸いです．


# 参考
[1] Android Developers，WorkManager でタスクのスケジュールを設定する，https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja

[2] Android Developers，他の Jetpack ライブラリで Hilt を使用する，https://developer.android.com/training/dependency-injection/hilt-jetpack?hl=ja

[3] M0710Fa，Jetpack ComposeとHiltによるDIのお勉強備忘録，https://qiita.com/m0710fa/items/ee19eac824d240944698

[4] Android Developers，WorkManager のカスタム構成と初期化，https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration?authuser=1&hl=ja

[5] Android Developers，WorkManager リリースノート，https://developer.android.com/jetpack/androidx/releases/work?authuser=1&hl=ja#2.6.0-alpha01