LoginSignup
2
3

More than 3 years have passed since last update.

【Android】WorkManager+MVVM+Hilt

Posted at

WorkManagerとは?

Android Jetpackのライブラリの一つ。
ForegroundServiceやJobSchedulerの代わりになる。
https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja

やりたいこと

  1. アプリから切り離して長時間の処理を行わせたい
  2. ロジック部分はWorkManager外に定義したい
  3. ついでに通知に結果を流し込みたい

1. アプリから切り離して長時間の処理を行わせたい

WorkManager基礎部分

MyWorker.kt
class MyWorker constructor(
    private val context: Context,
    private val params: WorkerParameters
) : CoroutineWorker(context, params) {
  override suspend fun doWork(): Result {
    // 処理
    return Result.success()
  }
}
MyViewModel.kt
class MyViewModel : ViewModel() {
  fun run(){
    WorkManager.getInstance(context)
            .enqueue(OneTimeWorkRequest.from(MyWorker::class.java))
  }
}

WorkerManagerをViewModel等から呼び出し、一度きり指定でMyWorkerを実行させます。
doWorkの中の処理がバックグラウンドで実行され、終わるとResult.success()が返却されます。

ForeGroundService化

MyWorker.kt
  override suspend fun doWork(): Result {
    setForeground(ForegroundInfo(notificationId, notificationBuilder.build()))
    // 処理
  }
}

普通にNotificationBuilderを生成したら(割愛)、ForegroundInfo()に突っ込んでsetForegroundを呼び出します。
ForegroundServiceと違ってWorkManager側が実行をForeground化するかどうか決定出来るので、見る場所がスマートになっていい感じ。

2. ロジック部分はWorkManager外に定義したい

MVVMパターンとHiltによるDIと組み合わせて、Repositoryに書いた処理をworkManagerから実行させたい。

だめなパターン.kt
@AndroidEntryPoint// つけられない
class MyWorker constructor(
  private val context: Context,
  private val params: WorkerParameters
) : CoroutineWorker(context, params) {
  @Inject
  lateinit var myRepository: MyRepository
  override suspend fun doWork(): Result {
    myRepository.doSomething()// not initialized
    return Result.success()
  }
}

WorkManagerはServiceではないので@AndroidEntryPointが設定出来ません。
そのため、Injectが作用せずnot initializedと怒られてしまいます。
ではどうするか。

MyApplication.kt
@HiltAndroidApp
class MyApplication @Inject constructor() : Application() {
  lateinit var myRepository: MyRepository
  override fun create(){
    WorkManager.initialize(
      this,
      Configuration.Builder()
        .setWorkerFactory(MyWorkerFactory(myRepository)).build()
    )
  }
}
MyWorkerFactory.kt
class ThetaWorkerFactory @Inject constructor(
  private val myRepository: MyRepository
) : WorkerFactory() {
  override fun createWorker(
      appContext: Context,
      workerClassName: String,
      workerParameters: WorkerParameters
  ): ListenableWorker? {
      return MyWorker(myRepository, appContext, workerParameters)
  }
}
MyWorker.kt
class MyWorker constructor(
  private val myRepository: MyRepository,
  private val context: Context,
  private val params: WorkerParameters
) : CoroutineWorker(context, params) {
  override suspend fun doWork(): Result {
    myRepository.doSomething()
    return Result.success()
  }
}
AndroidManifest.xml
<application>
  <provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:exported="false"
    tools:node="remove" />

ApplicationクラスでWorkerFactoryを上書きし、Workerに引数でrepositoryを渡すことで達成出来ました。
Workerは初期化が一度しか出来ないので、Manifestに初期化するなよとおまじないしなきゃいけないっていうやつは使うたび忘れそう気が利かない。

3. ついでに通知に結果を流し込みたい

長くなったので別記事に分けようと思います。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3