Welcome to the fifth post in our WorkManager series. WorkManager is an Android Jetpack library that runs deferrable, guaranteed background work when the work’s constraints are satisfied. It is the current best practice for this kind of work on Android.
If you’ve been following thus far, we’ve talked about:
- What WorkManager is and when to use WorkManager
- How to use the WorkManager API to schedule work
- WorkManager and Kotlin
- WorkManager Periodicity
In this article, we’re going to talk about custom configuration, covering:
- Why we may need a custom configuration
- How to define a custom configuration
- What is a
WorkerFactoryand why we need a custom one
- What is the
- Use Dagger to inject parameters in our
- On-Demand initialization
The two articles are connected and the second one requires knowledge presented in this one.
Stating the problem
When using WorkManager, it’s your responsibility to define
CoroutineWorker or any other
ListenableWorker derived classes. WorkManager instantiates your workers, at the right time, independently from having your application running in the foreground or running at all. To instantiate your worker class WorkManager uses a
Workers created by the default
WorkerFactory have access to only 2 parameters:
If you need to have additional parameters passed to your worker’s constructor, you need a custom
For the curious: We said that the default
WorkerFactoryuses reflection to instantiate the right
ListenableWorkerclass. This can fail if our workers class names are minimized by R8 (or ProGuard). To avoid that, WorkManager includes a
proguard-rules.profile that avoids obfuscation of your
Custom configuration and WorkerFactory
The WorkManager class follows the singleton pattern and it can only be configured before instantiation. This means that if you want a custom configuration, you need to disable the default configuration first.
If you try and initialize WorkManager a second time using the
initialize() method an exception (added in v1.0.0) will be thrown. To avoid this, disable the default initialization. You can then configure and initialize WorkManager in your Application’s
A newer, better way to initialize WorkManager has been added in v2.1.0. You can use an on-demand initialization by implementing WorkManager’s
Configuration.Provider interface in your
Application class. Then you just need to get the instance using
getInstance(context) and WorkManager will initialize WorkManager using your custom configuration.
As we already said, you can configure the
WorkerFactory used to create the workers, but you can also customize other parameters. The full list of parameters is in WorkManager’s reference guide for the
Configuration.Builder. Here I want to call out two additional parameters:
- Logging level
Modifying the logging level comes in handy when we need to understand what is going on with WorkManager. We have a documentation page on this topic. You can take a look at the Advanced WorkManager codelab to see how this is implemented in a real sample and what kind of information you can obtain.
We may want to customize the
JobId range, if we are using WorkManager as well as the JobScheduler API in our application. In this case you want to avoid using the same
JobId range in both places. There’s also a new Lint rule that covers this case introduced in v2.4.0.
We already know WorkManager has a default
WorkerFactory that uses reflection to find which class to instantiate based on the
Worker class name we passed in our
⚠️ If you create a
WorkRequestand then you refactor the app using a different class name for your worker, WorkManager will not be able to find the right class and will throw a
You probably want to add other parameters to your worker’s constructor. Imagine having a worker that expects a reference to a Retrofit service needed to communicate with a remote server:
If we make this change to an application it will still compile, but, as soon as we execute it and WorkManager tries to instantiate this
CoroutineWorker class, the application will be closed with an exception, complaining that it’s not possible to find the right init method to instantiate:
Caused by java.lang.NoSuchMethodException: <init> [class android.content.Context, class androidx.work.WorkerParameters]
We need a custom
But, not so fast: we already saw that there are few steps involved. Let’s recap what we have to do, then dive into each item details:
- Disable the default initialization
- Implement a custom
- Create a custom configuration
- Initialize WorkManager
Disable the default initialization
As described in WorkManager’s documentation, disabling has to be done in your
AndroidManifest.xml file, removing the node that is merged automatically from the WorkManager library by default.
Implement a custom WorkerFactory
We now need to write our own factory that creates our worker with the right parameters:
Create a custom WorkerConfiguration
Next, we have to register our factory in our WorkManager’s custom configuration:
This is all you need if you have a single Worker class type in your application. If you have more than one, or you expect to have more in the future, a better solution is to use the
DelegatingWorkerFactory introduced in v2.1.
Instead of configuring WorkManager to directly use our factory, we can use a
DelegatingWorkerFactory and add to it our own
WorkerFactory using its
addFactory() method. You can then have multiple factories where each one takes care of one or more workers. Register your factory with the
DelegatingWorkerFactory and it will take care to coordinate the multiple factories.
In this case your factory needs to check if the
workerClassName passed as a parameter is something that it knows how to handle. If not, it returns
null and the
DelegatingWorkerFactory will move to the next registered factory. If none of the registered factories know how to handle a class, it will fall back to the default factory that uses reflection.
Here’s our factory modified to return
null if it doesn’t know how to handle a
Our WorkManager configuration then becomes:
If you have more than one
Worker that requires different parameters, you can create a second factory and add it calling
addFactory a second time.
WorkManager is a powerful library, able to cover a lot of common use cases with its default configuration. However there are some cases when you need to increase its debugging level or need to pass additional parameters to your worker. In these cases you need a custom configuration.
I hope that this article has given you a good overview of this topic, let me know if you have any questions in the comments or contacting me directly on twitter at pfmaggi@.
The next article covers how to use Dagger in a WorkManager custom configuration: “Customizing WorkManager with Dagger”.