WorkManager in multi-module apps

TJ
Android Developers
Published in
2 min readMar 8, 2023

--

Background

Modularization is a great tool for building an app at scale, however care must be taken to ensure that resources shared between modules are properly instantiated. WorkManager provides a default configuration that is applied on app start up. This is often convenient, but in a modularized app where different modules often have different dependencies, WorkManager’s default configuration may not be enough. This blog post details how we set up WorkManager for use in the multi module Now in Android app.

Configuring the WorkManager Singleton

WorkManager exists as a singleton instance. To provide custom configuration for this instance in a single module app, you can implement a Configuration.Provider that provides parameters for the Executor used to perform work. You can also implement a WorkerFactory to aid in dependency injection when using WorkManager with Hilt.

Things get more nuanced in a multi-module app. Which module should be responsible for configuring the WorkManager instance? If such a module exists, wouldn’t it need to depend on all modules that need workers? Wouldn’t that break the “low cohesion” principle when modularizing apps? It would, so instead, we lean on another software engineering principle: delegation.

Creating a DelegatingWorker

WorkManager WorkerFactory instances can create Worker instances at runtime given:

When you couple this with Hilt entry points, it becomes possible to dynamically delegate to Worker instances lazily without having to implement the Configuration.Provider in a central location. The skeleton for such an implementation follows:

The above code determines what Worker the app should delegate to. To do so, it reads the fully qualified name of the Worker class from the WorkerParameters. Once it has that, it takes the HiltWorkerFactoryEntryPoint from the application Context and uses it to instantiate the given Worker.

See the following snippet for an example of a utility method you could use to pass the fully qualified name of the worker to the DelegatingWorker via the WorkerParameters:

To use the above function, create a WorkRequest targeting the DelegatingWorker, and then add any other metadata needed for the work in the delegated Worker. In the snippet to follow, the delegated Worker is the SyncWorker.

Finally, enqueue the work as usual:

Wrap up

Use of a DelegatingWorker instance is useful in more than just multi-module apps. It is also useful for libraries who need to use WorkManager but cannot pass their dependencies easily to the apps that depend on them.

A useful rule of thumb is, if you do not have convenient access to the Application instance in app and you need to call on WorkManager, use a delegating worker to lazily create your actual Worker with the necessary WorkerFactory.

--

--