web-dev-qa-db-ja.com

Dagger2:WorkManagerに依存関係を挿入できません

だから私が読んだことから、ダガーはまだWorkerでの注入をサポートしていません。しかし、人々が示唆するように、いくつかの回避策があります。私はオンラインの例に従って多くの方法でそれをやろうとしましたが、それらのどれも私のために働きません。

Workerクラスに何もインジェクトしようとしない場合、コードは正常に動作しますが、DAOとサービスにアクセスする必要があるため、必要なことを実行できません。これらの依存関係で@Injectを使用する場合、依存関係はnullであるか、ワーカーが起動しません。つまり、デバッガーがワーカークラスに入りさえしません。

たとえば、私はこれを試しました:

@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依存関係はnullのままです。

私は他の多くのことを試しましたが、どれもうまくいきませんでした。これを行う方法さえありますか?ありがとう!!

12
varunkr

概要

WorkerFactory を参照する必要があります。1.0.0-alpha09以降。

以前の回避策は、デフォルトの0-argコンストラクタを使用してWorkerを作成できることに依存していましたが、1.0.0-alpha10これはもはやオプションではありません。

WorkerというDataClearingWorkerサブクラスがあり、このクラスにはDaggerグラフからのFooが必要であるとしましょう。

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

    lateinit var foo: Foo

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

現在、これらのDataClearingWorkerインスタンスの1つを直接インスタンス化することはできません。そのため、それらのいずれかを作成できる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を作成する必要があります。これは、normalDaggerの方法で実行できます。

@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上記のブロックはinsideyour <applicationタグ..それはあなたのActivitiesServicesなどの兄弟です...

Applicationサブクラス(または必要に応じて他の場所)で、WorkManagerを手動で初期化できます。

@Inject
lateinit var workerFactory: WorkerFactory

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

    WorkManager.initialize(this, config)
}
20
Craig Russell

バージョン1.0.0-beta01の時点で、WorkerFactoryによるDaggerインジェクションの実装がここにあります。

コンセプトはこの記事からですhttps://medium.com/@nlg.tuan.kiet/bb9f474bde37 ステップごとの実装(in Kotlin)。

===========

達成しようとしているこの実装は次のとおりです。

ワーカーに依存関係を追加するたびに、関連するワーカークラスに依存関係を配置します

===========

1.すべての労働者の工場のインターフェースを追加

IWorkerFactory.kt

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

2.単純なWorkerクラスとファクトリを追加whichimplements 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を追加multi-binding

WorkerKey.kt

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

4.multi-binding workerの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.WorkerModuleAppComponentに入れます。ここでは、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. Applicationクラスでは、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を使用して定型コードを減らすなどの詳細については、冒頭で述べた記事を参照してください。

5
diousk

Dagger2 Multibindings を使用してこの問題を解決します。

同様のアプローチがViewModelオブジェクトの注入に使用されます(よく説明されています here )。ビューモデルの場合との重要な違いは、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を手動で初期化します(プロセスごとに1回だけ実行する必要があります)。マニフェストで自動初期化を無効にすることを忘れないでください。

マニフェスト:

    <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)
  1. ワーカーを使用

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

WorkManager機能の詳細については、こちらをご覧ください talk .

4
art

WorkManagerでalpha09新しい WorkerFactory があり、これを使用してWorkerを希望どおりに初期化できます。

  • WorkerApplicationContextを受け取る新しい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);
3
Rahul