Dagger2: Unable to inject dependencies in WorkManager

varunkr picture varunkr · Sep 21, 2018 · Viewed 7.5k times · Source

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 following examples online but none of them work for me.

When I don't try to inject anything into the Worker class, the code works fine, only that I can't do what I want because I need access to some DAOs and Services. If I use @Inject on those dependencies, the dependencies are either null or the worker never starts i.e the debugger doesn't even enter the Worker class.

For eg I tried doing this:

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

    void inject(MyWorker myWorker);
}

@Module
public class Module{

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

}

And in my worker

@Inject
MyRepository myRepo;

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

But then the execution never reaches the worker. If I remove the constructor, the myRepo dependency remains null.

I tried doing many other things but none work. Is there even a way to do this? Thanks!!

Answer

Craig Russell picture Craig Russell · Nov 19, 2018

Overview

You need to look at WorkerFactory, available from 1.0.0-alpha09 onwards.

Previous workarounds relied on being able to create a Worker using the default 0-arg constructor, but as of 1.0.0-alpha10 that is no longer an option.

Example

Let's say that you have a Worker subclass called DataClearingWorker, and that this class needs a Foo from your Dagger graph.

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

    lateinit var foo: Foo

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

Now, you can't just instantiate one of those DataClearingWorker instances directly. So you need to define a WorkerFactory subclass that can create one of them for you; and not just create one, but also set your Foo field too.

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
    }
}

Finally, you need to create a DaggerWorkerFactory which has access to the Foo. You can do this in the normal Dagger way.

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

Disabling Default WorkManager Initialization

You'll also need to disable the default WorkManager initialization (which happens automatically) and initialize it manually.

In the AndroidManifest.xml, you can disable it like this:

 <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" />

Be sure to replace com.your.app.package with your actual app's package. The <provider block above goes inside your <application tag.. so it's a sibling of your Activities, Services etc...

In your Application subclass, (or somewhere else if you prefer), you can manually initialize WorkManager.

@Inject
lateinit var workerFactory: WorkerFactory

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

    WorkManager.initialize(this, config)
}