Dagger2: Unable to inject dependencies in WorkManager

前端 未结 4 939
庸人自扰
庸人自扰 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

    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.

    1. 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
          }
      }
      
    2. 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)
      
    3. Define worker binding.

      @Module
      interface HardWorkerModule {
      
          @Binds
          @IntoMap
          @WorkerKey(HardWorker::class)
          fun bindHardWorker(worker: HardWorker): Worker
      }
      
    4. 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
      }
      
    5. 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) }
      }
      
    6. 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)
    
    1. Use worker

      val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.java)
      WorkManager.getInstance().enqueue(request)
      

    Watch this talk for more information on WorkManager features.

提交回复
热议问题