So from what I read, Dagger doesn\'t have support for inject in Worker yet. But there are some workarounds as people suggest. I have tried to do it a number of ways followin
I use Dagger2 Multibindings to solve this problem.
The similar approach is used to inject ViewModel
objects (it's described well here). Important difference from view model case is the presence of Context
and WorkerParameters
arguments in Worker
constructor. To provide these arguments to worker constructor intermediate dagger component should be used.
Annotate your Worker
's constructor with @Inject
and provide your desired dependency as constructor argument.
class HardWorker @Inject constructor(context: Context,
workerParams: WorkerParameters,
private val someDependency: SomeDependency)
: Worker(context, workerParams) {
override fun doWork(): Result {
// do some work with use of someDependency
return Result.SUCCESS
}
}
Create custom annotation that specifies the key for worker multibound map entry.
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class WorkerKey(val value: KClass)
Define worker binding.
@Module
interface HardWorkerModule {
@Binds
@IntoMap
@WorkerKey(HardWorker::class)
fun bindHardWorker(worker: HardWorker): Worker
}
Define intermediate component along with its builder. The component must have the method to get workers map from dependency graph and contain worker binding module among its modules. Also the component must be declared as a subcomponent of its parent component and parent component must have the method to get the child component's builder.
typealias WorkerMap = MutableMap, Provider>
@Subcomponent(modules = [HardWorkerModule::class])
interface WorkerFactoryComponent {
fun workers(): WorkerMap
@Subcomponent.Builder
interface Builder {
@BindsInstance
fun setParameters(params: WorkerParameters): Builder
@BindsInstance
fun setContext(context: Context): Builder
fun build(): WorkerFactoryComponent
}
}
// parent component
@ParentComponentScope
@Component(modules = [
//, ...
])
interface ParentComponent {
// ...
fun workerFactoryComponent(): WorkerFactoryComponent.Builder
}
Implement WorkerFactory
. It will create the intermediate component, get workers map, find the corresponding worker provider and construct the requested worker.
class DIWorkerFactory(private val parentComponent: ParentComponent) : WorkerFactory() {
private fun createWorker(workerClassName: String, workers: WorkerMap): ListenableWorker? = try {
val workerClass = Class.forName(workerClassName).asSubclass(Worker::class.java)
var provider = workers[workerClass]
if (provider == null) {
for ((key, value) in workers) {
if (workerClass.isAssignableFrom(key)) {
provider = value
break
}
}
}
if (provider == null)
throw IllegalArgumentException("no provider found")
provider.get()
} catch (th: Throwable) {
// log
null
}
override fun createWorker(appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters) = parentComponent
.workerFactoryComponent()
.setContext(appContext)
.setParameters(workerParameters)
.build()
.workers()
.let { createWorker(workerClassName, it) }
}
Initialize a WorkManager
manually with custom worker factory (it must be done only once per process). Don't forget to disable auto initialization in manifest.
manifest:
Application onCreate
:
val configuration = Configuration.Builder()
.setWorkerFactory(DIWorkerFactory(parentComponent))
.build()
WorkManager.initialize(context, configuration)
Use worker
val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.java)
WorkManager.getInstance().enqueue(request)
Watch this talk for more information on WorkManager
features.