Android

How to pass custom parameters to RxWorker using Dagger 2

How to access Retrofit or custom parameters in an Android RxWorker class. I’ll guide you step by step how we can achieve that using Dagger. Read on!

Udayaditya Barua
Muuzzer
Published in
6 min readJul 24, 2020

--

WorkManager is the latest solution by google which is very helpful in running background tasks. Under the hood it uses a combination of JobScheduler (API 23+) and BroadcastReceiver + AlarmManager (API 14–22). This basically creates a very powerful solution for scheduling or performing immediate background tasks along with constraints like Network, Disk Space etc. If you’re not familiar about the WorkManager, I suggest you do a WorkManager Codelab first.

Before we start, this article is for people who are familiar with Dagger 2 and have implemented Dagger 2 in their project.

That being said, we not here to discuss WorkManager, but rather a specific problem; injecting custom parameters into the Worker class. Now why is this important?

Consider a Worker class (which needs to be defined in order to schedule a task):

Here you can see, it accepts 2 parameters Context and WorkerParameters. The problem here is, you cannot instantiate the worker class directly. Don’t get alarmed by RxWorker. It extends Listenable worker. The methodology is the same for all types.

If you remember, the way to schedule a worker is using the WorkManager class.

As you can see, we don’t have any control over the object creation. Now the problem here is, what if you needed an instance of the database, or your Retrofit instance to call your backend API. How are you going to send those parameters?

The answer is Dagger.

Say Hello To WorkerFactory

What is a WorkerFactory?

A factory object that creates ListenableWorker instances. The factory is invoked every time a work runs

In other words, if your worker factory class is registered to WorkManager, then it’ll get called each time to create an instance of the worker class which we discussed above. That means, now you have control over the constructor parameters!

I am here to guide you weave all the components together to get a hassle free worker class and dagger implementation

Step By Step Implementation

First, we’ll start by creating an Annotation:

Why is this necessary?

Since we’ll be using Dagger and WorkerFactory, we need a way to give the factory the data to create instances of our worker(s)

Here we create a Map of .class vs instances of the workers we have in our code base using this annotation. We then provide this map to our WorkerFactory class to iterate and use the concrete instance from the map.
The map will look something like this:
Map<Class<? extends RxWorker>, Provider<RxWorker>> getWorkerMap();

Provider is of type java.inject used by Dagger to get instances. Don’t worry about it too much. Just remember this piece of code.

Create a Binding Module — Key to the value

Ok, now we need to use the annotation somewhere. If you remember, this sort of implementation is done when designing the framework for injecting ViewModels in our activities. Similarly here, we need to create a module to bind the key to the value.

Simple enough? SampleWorker.class on line 6 is the key, RxWorker on line 7 is the value for the Map. Dagger then tries to create instances of the value objects.

Add the custom parameter to the worker class we want and annotate @Inject

Coming back to our worker class, say we want to inject Retrofit in our worker class. We add the parameter and annotate the constructor with @Inject like dagger wants us to do.

Now, when dagger tries to create this map and fill the values with the instances, it’ll invoke our custom constructor of the worker.

But there’s an important step missing here, how will it inject Retrofit? Hold that thought! We’ll come back to this later.

So, what do we have so far? We have:

  • Annotation defining a key for Class vs Instance map
  • Binder module which specifies the map dagger needs to create
  • Our custom worker class which will actually perform our background task

We need to tell dagger to attach this module to its graph so that it can start injecting, right? You may want to hit build and see whats going on till now.

The thing is, it throws an error complaining that it cannot inject Retrofit. Specifically Retrofit cannot be injected without @Provides annotation.

Now for dagger to inject Retrofit, it needs the instance of Retrofit from somewhere right. I am going out on a limb here and assuming you have a Repository module with you which has all the @Provides for Retrofit, OkHttp and works. If you don't, best you create one.

Remember the WorkerBinding module above? Include the Repository Module here:

@Module(includes = RepositoryModule.class)
public abstract class WorkerBindingModule { ...

The only thing left is adding this module to the parent component so that dagger recognises it. Go ahead and do that.

Add the WorkerBinding module in AppComponent

Sounds simple right? So go ahead and add this module in the list of modules in the app component and hit build.

At this point, your build will fail and dagger will spew out a whole lot of errors. Most prominently, it’ll say cannot bind Retrofit in 2 instances and it'll print the entire graph.

So what does this mean?

You will not get this error if you haven’t used repository pattern and not included retrofit anywhere else.

This error is basically thrown because:

  • We have created a binding (the map which we talked earlier) for the ViewModels also! Which in turn include Retrofit.
  • We have included WorkerBindingModule in the top-level i.e., the app component level.

The graph has become something like this:

Rough hand sketch trying to show the dagger graph. The left side is the graph we are making, the right one is the existing
Rough sketch of the dagger graph

As shown, on one side, Retrofit is getting injected in the level SubComponent, but here, we try to directly inject it. Dagger doesn't like this. More so, it'll not be able to figure which class needs which particular instance of Retrofit.

Ok, so you probably guessed what we need to do now. Remove that line where we have included the worker binding module in the AppComponent and hit build.

I just wanted to show this error so that you understand what is going on here.

Now dagger will not complain. But now, our module is out of the graph, so it will not do anything i.e., there will no injection happening. We still need to integrate it into the graph.

Enter SubComponent — Create a worker SubComponent

If you notice, we didn’t include Context here. Why, because of the same problem, it's injected on a different level. Context is provided at the AppComponent level and not in sub component level. Don’t worry, you’ll get the context. Wait on.
In order to create this Map, we have included our WorkerBindingModule here as well. This provides dagger with the necessary requirements for creating this map.
So when dagger starts to populate the map (as you will see where), it’ll call our Worker constructor with the extra Retrofit parameter.

Create the WorkerFactory Class

Comes the part where we guide android how to create our worker class using our custom factory.

Yes, it looks a little complicated but trust me it’s quite simple.
We had dagger inject the subcomponent and we called it’s build method to create the concrete WorkManagerFactorySubComponent by passing work parameters on line 13.

This prompted dagger to populate that map I specified earlier. Since it already has everything it needs, it’s able to inject the custom Retrofit parameter as well. This code is basically looping through that map and giving the WorkManager the correct Worker instance.

Remember I talked about getting the context? The thing is, we don’t need it here. Because we are going to initialise our worker manager at the application level i.e., AppComponent level; which already has the applicationContext.

Initialise WorkManager in Application Class

By default, WorkManager initialises itself using it own mechanism of ContentProviders and factory. But since we have our own factory, we need to tell the WorkManager explicitly not to go the default route.

Our Application class becomes:

And in the manifest also:

This is to prevent WorkerManager to use its default ContentProvider to create the workers.

That’s it! Hit build and see the magic unfold. You will find a lot of ways to do this but I feel this is the simplest and least complicated.

Now any new worker class you create, add it to the WorkerBindingModule and you're good to go.

I used this technique in my own application and it works flawlessly. Try it out!

--

--

Udayaditya Barua
Muuzzer
Editor for

Coffee aficionado | Proud Entrepreneur | Food fan | Coding Ninja | Gamer | Beer Geek | Problem Solver