Dagger2: Unable to inject dependencies in WorkManager

前端 未结 4 938
庸人自扰
庸人自扰 2020-12-24 07:55

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

4条回答
  •  北海茫月
    2020-12-24 08:22

    2020/06 Update

    Things become much easier with Hilt and Hilt for Jetpack.

    With Hilt, all you have to do is

    1. add annotation @HiltAndroidApp to your Application class
    2. inject out-of-box HiltWorkerFactory in the field fo Application class
    3. 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 {
        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, // provide from AppModule
            private val apiService: Provider // provide from NetworkModule
        ) : IWorkerFactory {
            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)
    

    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
        // 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 {
        @Component.Builder
        abstract class Builder: AndroidInjector.Builder()
    }
    

    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, @JvmSuppressWildcards Provider>>
    ) : 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 {
            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

    
    

    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.

提交回复
热议问题