Post

Modular architecture- Using Dagger Multibinding to initialize modules

As Android codebases continue to become increasingly modular, some of these modules might need to be initialized at app startup in order to wire everything up. This usually results in the Application class or the Splash/Main activity, calling the “managers/helpers” of these modules, providing them with the dependencies, and asking them to initialize themselves. If an app has a few modules, then this might not be an issue but as the number of modules increase, this management of their initialization can quickly become unwieldy.

Through this post, we’ll explore how we can use Dagger multibindings to make this easier. If you aren’t familiar with Multibindings and @IntoSet/@IntoMap, then I would advise reading the documentation before proceeding. I’ll wait.

Disclaimer: This is only for modules that are self-contained and need to be informed about app launch.

Create a ModuleInitializer interface

First up, we’ll create a ModuleInitializer interface that will be implemented by any gradle module that needs to be instantiated at app startup. We’ll add this interface to our “shared” module (a common module on which every module depends)

The interface only exposes one method, initialize which returns a Completable. If the module creation is synchronous or it doesn’t need to block the app launch process, one can simply return Completable.complete. Using a Completable allows us to optionally wait for all the module initialization to complete before proceeding with the app launch process.

Next, any gradle module that needs an initialization step, can implement this interface and define its dependencies that will be provided by Dagger. For example, at Viki, we have a customercare module that houses all our fragments and activities for customer support and uses the Zendesk SDK internally. The Zendesk SDK needs to be initialized at app launch. Similarly, we also have a subscription module that needs to establish a connection with Google Play Store at launch.

Create a Set of Initializers

Now in the app module, we will create a new dagger module that will provide these ModuleInitializers. Through multibinding, Dagger assembles the collection so that the application code can inject it without depending directly on the individual bindings. We’ll also update our AppComponent to provide this set of ModuleInitializer

Now in our Application or Splash/Main Activity, we can get this set of ModuleIntializers and initialize each of them. If all modules can be initialized independently, you can use Completable.merge to kick off their initialization process.

If your modules need to be initialized sequentially, then we can use concatMap instead of merge.

What if customercare depends on subscription to be ready?

To support a gradle module being dependent on the prior initialization of another gradle module, we’ll update the ModuleInitializer and add a dependencies method to the interface (which by default returns an empty list, i.e. no dependencies)

Next, we’ll update the CustomerCareInitializer to return SubscriptionInitializer as its dependency.

Now instead of initializing every module from the set, we need to check the dependencies of each module, and if the dependency hasn’t been initialized yet, we need to initialize it before we initialize the module itself. Since the dependencies is a list of class, we would need to update our Dagger Module to Bind into a map rather than into a set. Here the map key will be the class of the corresponding ModuleInitializer

Lastly, we’ll need to update how we generate the initializing Completable that is executed at app launch. Instead of iterating over the set, we now have a map and each module has its own list of dependencies. Therefore, we will iterate through each entry in the map, look at the dependencies, and if there are dependencies, we will first initialize them. For every module we initialize, we will also update a local store of initialized modules. Every time we need to initialize a module, we’ll first check the local store to make sure this module hasn’t already been initialized as a part of another dependency. This will prevent us from initializing the same module twice (in case multiple modules initializers depend on a single module initializer).

In the future, if we want to add a new module that requires initialization at app launch, we’ll create a new implementation of the ModuleInitializer interface inside the new module and bind that to the map/set in the dagger InitializerModule. The place where we iterate over the map/set and instantiate each module will not need any changes, making this process a lot more manageable.

How does this compare to JetPack App Startup

App Startup is a new Jetpack library that was recently announced by Google. From the docs:

The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

While this is a great initiative and hopefully will be adopted by libraries such as Firebase, Facebook, and others (which use content providers to instantiate themselves), the current API surface is not sufficient for app developers who want to instantiate their own modules. The library uses a content provider to provide a way to call each of our component initializers. Content providers are automatically created by the system and applications usually do not create ContentProvider instances directly. As a result, Content Provider will be created before the call to Application#onCreate as mentioned in the docs:

Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created.

Therefore, it is not possible to provide these component initializers, defined by the App Startup Library, with dependencies from your dagger graph as most applications create their AppComponent in Application#onCreate.

Conclusion

As we make our codebases modular, we don’t want to bloat up our Application class with logic on instantiating these modules. Changes to the module internals might cascade to the app module as it might need different dependencies, or the module initialization might change from being synchronous to asynchronous or vice-versa. Dagger Mulibinding can provide a good alternative to manually instantiating individual modules at app launch.

Big thanks to Márton Braun for reviewing the post.

That’s it! If you have any questions or suggestions, leave a comment below. If you want to be notifed of such posts, please follow me on Twitter.

This post is licensed under CC BY 4.0 by the author.