Dagger2: невозможно ввести зависимости в WorkManager - PullRequest
0 голосов
/ 21 сентября 2018

Итак, из того, что я прочитал, Dagger пока не поддерживает инъекцию в Worker.Но есть некоторые обходные пути, которые люди предлагают.Я попытался сделать это несколькими способами, следуя примерам онлайн, но ни один из них не работает для меня.

Когда я не пытаюсь внедрить что-либо в класс Worker, код работает нормально, только я не могу делать то, что хочу, потому что мне нужен доступ к некоторым DAO и службам.Если я использую @Inject для этих зависимостей, зависимости будут либо нулевыми, либо работник никогда не запустится, т. Е. Отладчик даже не входит в класс Worker.

Например, я попытался сделать это:

@Component(modules = {Module.class})
public interface Component{

    void inject(MyWorker myWorker);
}

@Module
public class Module{

    @Provides
    public MyRepository getMyRepo(){
        return new myRepository();
    }

}

А в моем работнике

@Inject
MyRepository myRepo;

public MyWorker() {
    DaggerAppComponent.builder().build().inject(this);
}

Но тогда исполнение никогда не достигнет работника.Если я удалю конструктор, зависимость myRepo останется нулевой.

Я пытался делать много других вещей, но ничего не получалось.Есть ли способ сделать это?Спасибо !!

Ответы [ 4 ]

0 голосов
/ 01 января 2019

Начиная с версии 1.0.0-beta01 , здесь приведена реализация инъекции Кинжала с WorkerFactory.

Концепция взята из этой статьи : https://medium.com/@nlg.tuan.kiet/bb9f474bde37 и я просто публикую свою собственную реализацию этого шага ( в Kotlin ).

==========

Чтоэта реализация пытается достичь:

Каждый раз, когда вы хотите добавить зависимость к работнику, вы помещаете эту зависимость в связанный класс работника

===========

1. Добавить интерфейс для всех рабочих завода

IWorkerFactory.kt

interface IWorkerFactory<T : ListenableWorker> {
    fun create(params: WorkerParameters): T
}

2. Добавить простой рабочий класс с фабрикой , который реализует IWorkerFactory , а также с зависимостью для этого работника

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. Добавление WorkerKey для Dagger's multi-binding

WorkerKey.kt

@MapKey
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WorkerKey(val value: KClass<out ListenableWorker>)

4. Add модуль Dagger для мультисвязывающего работника (на самом деле мультисвязывает с завода)

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. Поместите WorkerModule в AppComponent .Здесь я использую dagger-android для создания класса компонента

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. Добавление пользовательского WorkerFactory в использовать возможность создания работника с момента выпуска версии 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. В классе приложений замените WorkerFactory на свой собственный:

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. Не забудьте отключить инициализацию менеджера работы по умолчанию

AndroidManifest.xml

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:enabled="false"
    android:exported="false"
    tools:replace="android:authorities" />

Вот и все.

Каждый раз, когда вы хотите добавитьзависимость от работника, вы помещаете зависимость в связанный рабочий класс (например, HelloWorker здесь).

Каждый раз, когда вы хотите добавить работника, реализуйте фабрику в рабочем классе и добавьте фабрику работника в WorkerModuleдля мульти-связывания.

Для получения более подробной информации, например, с помощью AssistedInject для сокращения стандартных кодов,пожалуйста, обратитесь к статье, которую я упомянул в начале.

0 голосов
/ 11 ноября 2018

Я использую Dagger2 Multibindings , чтобы решить эту проблему.

Аналогичный подход используется для инъекции ViewModel объектов (это хорошо описано здесь ).Важным отличием от модели представления является наличие аргументов Context и WorkerParameters в конструкторе Worker.Чтобы предоставить эти аргументы рабочему конструктору, следует использовать промежуточный компонент кинжала.

  1. Аннотируйте конструктор Worker с помощью @Inject и предоставьте желаемую зависимость в качестве аргумента конструктора.

    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. Создание пользовательской аннотации, в которой указан ключ для записи многосвязной рабочей карты.

    @MustBeDocumented
    @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
    @Retention(AnnotationRetention.RUNTIME)
    @MapKey
    annotation class WorkerKey(val value: KClass<out Worker>)
    
  3. Определение рабочей привязки.

    @Module
    interface HardWorkerModule {
    
        @Binds
        @IntoMap
        @WorkerKey(HardWorker::class)
        fun bindHardWorker(worker: HardWorker): Worker
    }
    
  4. Определите промежуточный компонент вместе с его конструктором и модулем, который будет предоставлять Context и WorkerParameters объекты.Компонент должен иметь метод для получения рабочих карт из графа зависимостей и содержать модуль рабочих привязок среди своих модулей.Также компонент должен быть объявлен как подкомпонент его родительского компонента, а родительский компонент должен иметь метод для получения компоновщика дочернего компонента.

    @Module
    class ArgumentsModule(private val appContext: Context,
                          private val workerParameters: WorkerParameters) {
    
        @Provides
        fun provideAppContext() = appContext
    
        @Provides
        fun provideWorkerParameters() = workerParameters
    }
    
    typealias WorkerMap = MutableMap<Class<out Worker>, Provider<Worker>>
    
    @Subcomponent(modules = [
        ArgumentsModule::class,
        HardWorkerModule::class])
    interface WorkerFactoryComponent {
    
        fun workers(): WorkerMap
    
        @Subcomponent.Builder
        interface Builder {
            fun argumentsModule(module: ArgumentsModule): Builder
            fun build(): WorkerFactoryComponent
        }
    }
    
    // some module of the parent component
    @Module(subcomponents = [WorkerFactoryComponent::class
                //, ...
            ])
    class ParentComponentModule {
        // ...
    }
    
    // parent component
    @ParentComponentScope
    @Component(modules = [ParentComponentModule::class
                //, ...
            ])
    interface ParentComponent {
    
        // ...
    
        fun workerFactoryComponent(): WorkerFactoryComponent.Builder
    }
    
  5. Реализация WorkerFactory.Он создаст промежуточный компонент, получит карту работников, найдет соответствующего провайдера работника и создаст запрошенного работника.

    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()
                .argumentsModule(ArgumentsModule(appContext, workerParameters))
                .build()
                .run { createWorker(workerClassName, workers()) }
    }
    
  6. Инициализируйте WorkManager вручную с фабрикой пользовательских рабочих станций (это необходимобыть сделано только один раз за процесс).Не забудьте отключить автоматическую инициализацию в манифесте.

манифест:

    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="${applicationId}.workmanager-init"
        android:exported="false"
        tools:node="remove" />

Приложение onCreate:

    val configuration = Configuration.Builder()
            .setWorkerFactory(DIWorkerFactory(parentComponent))
            .build()
    WorkManager.initialize(context, configuration)

Использовать рабочий

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

Посмотрите этот разговор для получения дополнительной информации о WorkManager функциях.

0 голосов
/ 19 ноября 2018

Обзор

Вам нужно взглянуть на WorkerFactory , доступный с 1.0.0-alpha09 и далее.

Предыдущие обходные пути основывались на возможности создания Worker с использованиемконструктор 0-arg по умолчанию, но с 1.0.0-alpha10 это больше не вариант.

Пример

Допустим, у вас есть подкласс Worker с именем DataClearingWorker, и этокласс нуждается в Foo из вашего графа Даггера.

class DataClearingWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    lateinit var foo: Foo

    override fun doWork(): Result {
        foo.doStuff()
        return Result.SUCCESS
    }
}

Теперь вы не можете просто создать один из этих экземпляров DataClearingWorker напрямую.Таким образом, вам нужно определить подкласс WorkerFactory, который может создать один из них для вас;и не только создать его, но и установить поле Foo.

class DaggerWorkerFactory(private val foo: Foo) : WorkerFactory() {

    override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? {

        val workerKlass = Class.forName(workerClassName).asSubclass(Worker::class.java)
        val constructor = workerKlass.getDeclaredConstructor(Context::class.java, WorkerParameters::class.java)
        val instance = constructor.newInstance(appContext, workerParameters)

        when (instance) {
            is DataClearingWorker -> {
                instance.foo = foo
            }
            // optionally, handle other workers               
        }

        return instance
    }
}

Наконец, вам нужно создать DaggerWorkerFactory, который имеет доступ к Foo.Это можно сделать обычным образом 1029 * Кинжал.

@Provides
@Singleton
fun workerFactory(foo: Foo): WorkerFactory {
    return DaggerWorkerFactory(foo)
}

Отключение инициализации WorkManager по умолчанию

Вам также необходимо отключить инициализацию WorkManager по умолчанию (чтопроисходит автоматически) и инициализируйте его вручную.

В AndroidManifest.xml вы можете отключить его следующим образом:

 <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="com.your.app.package.workmanager-init"
        android:enabled="false"
        android:exported="false"
        tools:replace="android:authorities" />

Обязательно замените com.your.app.package с пакетом вашего фактического приложения.Блок <provider выше содержит внутри вашего <application тега ... так что это брат вашего Activities, Services etc ...

В вашем Application подклассе, (или где-то еще, если вы предпочитаете), вы можете вручную инициализировать WorkManager.

@Inject
lateinit var workerFactory: WorkerFactory

private fun configureWorkManager() {
    val config = Configuration.Builder()
        .setWorkerFactory(workerFactory)
        .build()

    WorkManager.initialize(this, config)
}
0 голосов
/ 21 сентября 2018

В WorkManager alpha09 есть новый WorkerFactory , который можно использовать для инициализации Worker так, как вы хотите.

  • Используйте новый Workerконструктор, который принимает ApplicationContext и WorkerParams.
  • Зарегистрируйте реализацию WorkerFactory через Configuration.
  • Создайте configuration и зарегистрируйте вновь созданный WorkerFactory.
  • Инициализируйте WorkManager с этой конфигурацией (при удалении ContentProvider, который инициализирует WorkManager от вашего имени).

Вам необходимо сделать следующее:

public DaggerWorkerFactory implements WorkerFactory {
  @Nullable Worker createWorker(
  @NonNull Context appContext,
  @NonNull String workerClassName,
  @NonNull WorkerParameters workerParameters) {

  try {
      Class<? extends Worker> workerKlass = Class.forName(workerClassName).asSubclass(Worker.class);
      Constructor<? extends Worker> constructor = 
      workerKlass.getDeclaredConstructor(Context.class, WorkerParameters.class);

      // This assumes that you are not using the no argument constructor 
      // and using the variant of the constructor that takes in an ApplicationContext
      // and WorkerParameters. Use the new constructor to @Inject dependencies.
      Worker instance = constructor.newInstance(appContext,workerParameters);
      return instance;
    } catch (Throwable exeption) {
      Log.e("DaggerWorkerFactory", "Could not instantiate " + workerClassName, e);
      // exception handling
      return null;
    }
  }
}

// Create a configuration
Configuration configuration = new Configuration.Builder()
  .setWorkerFactory(new DaggerWorkerFactory())
  .build();

// Initialize WorkManager
WorkManager.initialize(context, configuration);
...