2020/06 Update
Things become much easier with Hilt and Hilt for Jetpack.
With Hilt, all you have to do is
- add annotation
@HiltAndroidApp
to your Application class
- inject out-of-box
HiltWorkerFactory
in the field fo Application class
- Implement interface
Configuration.Provider
and return the injected work factory in Step 2.
Now, change the annotation on the constructor of Worker from @Inject
to @WorkerInject
class ExampleWorker @WorkerInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
someDependency: SomeDependency // your own dependency
) : Worker(appContext, workerParams) { ... }
That's it!
(also, don't forget to disable default work manager initialization)
===========
Old solution
As of version 1.0.0-beta01, here is an implementation of Dagger injection with WorkerFactory.
The concept is from this article: https://medium.com/@nlg.tuan.kiet/bb9f474bde37 and I just post my own implementation of it step by step(in Kotlin).
===========
What's this implementation trying to achieve is:
Every time you want to add a dependency to a worker, you put the dependency in the related worker class
===========
1. Add an interface for all worker's factory
IWorkerFactory.kt
interface IWorkerFactory<T : ListenableWorker> {
fun create(params: WorkerParameters): T
}
2. Add a simple Worker class with a Factory which implements IWorkerFactory and also with the dependency for this worker
HelloWorker.kt
class HelloWorker(
context: Context,
params: WorkerParameters,
private val apiService: ApiService // our dependency
): Worker(context, params) {
override fun doWork(): Result {
Log.d("HelloWorker", "doWork - fetchSomething")
return apiService.fetchSomething() // using Retrofit + RxJava
.map { Result.success() }
.onErrorReturnItem(Result.failure())
.blockingGet()
}
class Factory @Inject constructor(
private val context: Provider<Context>, // provide from AppModule
private val apiService: Provider<ApiService> // provide from NetworkModule
) : IWorkerFactory<HelloWorker> {
override fun create(params: WorkerParameters): HelloWorker {
return HelloWorker(context.get(), params, apiService.get())
}
}
}
3. Add a WorkerKey for Dagger's multi-binding
WorkerKey.kt
@MapKey
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WorkerKey(val value: KClass<out ListenableWorker>)
4. Add a Dagger module for multi-binding worker (actually multi-binds the factory)
WorkerModule.kt
@Module
interface WorkerModule {
@Binds
@IntoMap
@WorkerKey(HelloWorker::class)
fun bindHelloWorker(factory: HelloWorker.Factory): IWorkerFactory<out ListenableWorker>
// every time you add a worker, add a binding here
}
5. Put the WorkerModule into AppComponent. Here I use dagger-android to construct the component class
AppComponent.kt
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class, // provides ApiService
AppModule::class, // provides context of application
WorkerModule::class // <- add WorkerModule here
])
interface AppComponent: AndroidInjector<App> {
@Component.Builder
abstract class Builder: AndroidInjector.Builder<App>()
}
6. Add a custom WorkerFactory to leverage the ability of creating worker since the release version of 1.0.0-alpha09
DaggerAwareWorkerFactory.kt
class DaggerAwareWorkerFactory @Inject constructor(
private val workerFactoryMap: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<IWorkerFactory<out ListenableWorker>>>
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
val entry = workerFactoryMap.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
val factory = entry?.value
?: throw IllegalArgumentException("could not find worker: $workerClassName")
return factory.get().create(workerParameters)
}
}
7. In Application class, replace WorkerFactory with our custom one:
App.kt
class App: DaggerApplication() {
override fun onCreate() {
super.onCreate()
configureWorkManager()
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
@Inject lateinit var daggerAwareWorkerFactory: DaggerAwareWorkerFactory
private fun configureWorkManager() {
val config = Configuration.Builder()
.setWorkerFactory(daggerAwareWorkerFactory)
.build()
WorkManager.initialize(this, config)
}
}
8. Don't forget to disable default work manager initialization
AndroidManifest.xml
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:enabled="false"
android:exported="false"
tools:replace="android:authorities" />
That's it.
Every time you want to add a dependency to a worker, you put the dependency in the related worker class (like HelloWorker here).
Every time you want to add a worker, implement the factory in the worker class and add the worker's factory to WorkerModule for multi-binding.
For more detail, like using AssistedInject to reduce boilerplate codes, please refer to the article I mentioned at beginning.