A Friendly Introduction to Dagger 2: Part 2

Using modules to specify how objects should be constructed

In the previous part of this series, we saw how Dagger 2 allows us to use dependency injection in our applications without having to write long blocks of initialization code by hand.

As you recall, we created an interface, to let the framework know what types of objects our main method was interested in, and Dagger automatically generated a concrete class that was able to construct instances of those types for us. We never specified how to actually construct these objects or their dependencies. Since all of our classes were concrete and properly annotated, this was not a problem: Dagger could infer, from the annotations, which constructors to invoke whenever an instance of each class was needed.

Very often, however, classes depend not on concrete, but on abstract classes and interfaces, which have no constructors that can be invoked by Dagger. Sometimes, modifying the source code of a class to include annotations might not be an option. At other times, creating an object requires more than simply invoking a constructor. In all these cases, Dagger’s automatic behavior is not sufficient, and the framework needs our help.

In today’s post, we will see how to provide additional instructions to Dagger about the construction of some objects by using modules. Modules can be easily swapped by each other and can be reused across projects. They can also receive running time arguments, which makes them very flexible.

An example

To illustrate the situation described above, let’s go back to first example of part 1, where we only had three classes: WeatherReporter and its two dependencies, LocationManager and WeatherService. Both dependencies were concrete classes. In practice, this would probably not be the case.

Let’s suppose, instead, that WeatherService is an interface, and that we have one more class, say, YahooWeather, that implements that interface:

If we try to compile the project again, Dagger will give us an error saying that it could not find a provider for WeatherService.

When a class is concrete and has an annotated constructor, Dagger can automatically generate a provider for that class. Since WeatherService is an interface, however, we must give Dagger more information.

What are modules?

For now, modules are classes that are able to build objects of certain types. The following module, for example, is able to create WeatherService objects upon request by instantiating the class YahooWeather.

Modules should receive the @Module annotation. Some of their methods, known as provider methods, receive the Provides annotation to indicate that they can provide instances of a certain type upon request. The names of the methods are not important: Dagger only looks at the signatures.

With this module definition, we enhance Dagger’s abilities to create objects and satisfy dependencies. Before, only concrete classes with annotated constructors were allowed as dependencies, but now, with this module, any class can depend on our WeatherService interface. We just need to connect this module to the component that our entry point uses:

The project now compiles again, and every WeatherReporter constructed by the method getWeatherReporter is given an instance of YahooWeather.

We are free to either define one huge module that knows how to build every abstract class and interface in the application, or many small modules that only know how to build closely related classes. In this series, we will use the latter approach.

How to swap modules

One of the most interesting properties of modules is that they can be easily swapped by each other. This allows us to have multiple implementations of an interface in our project, and to easily switch between.

Suppose, for example, that we have another concrete class, WeatherChannel, that also implements WeatherService. If we want to use this concrete class in our WeatherReporter instead of the previous YahooWeather, we can write a new module WeatherChannelModule and then replace the previous module by this one in our component, as follows.

Swapping modules is useful, for example, when writing integration or end-to-end tests. We can define another component which is almost identical to the production one, but that uses some different modules. I will go into more details about this in another post.

If we try to plug two different modules that provide the same type into a component, Dagger will throw a compilation error, telling us that the type is bound multiple times. For example, we are not allowed to do this:

Also, since Dagger 2 generates the components during compilation, modules cannot be replaced during running time. Our entry point, however, can have multiple components at its disposal, and decide which component to ask objects from according to some running time conditions.

Building more complex objects

Now that we know what modules are and how to write some basic ones, let’s see how can we write modules that build some more complex objects. For example, objects defined by third-parties (whose classes cannot be modified), that have dependencies, or that require configuration.

Building third-party objects

Sometimes modifying a class to include annotations is not an option. The class, for example, might be provided by a framework or some external library. Third-party classes often have very questionable design too, and might not lend themselves easily to dependency injection frameworks.

Suppose, for example, that our LocationManager class depends on GpsSensor. This class, however, was provided by the Acme Corporation, and cannot be modified. To make things worse, the constructor for this class doesn’t fully initialize it. After instantiating this class, we still need to call a number of methods, such as calibrate, before the object can actually be used. Below is the source code for LocationManager and GpsSensor.

Note that GpsSensor has no annotations. Since Acme Corporation’s products just work, they have no need for dependency injection or tests.

We would like to avoid having the constructor of LocationManager call the calibrate method since setting up a GpsSensor is not its responsibility. Ideally, all dependencies received should be ready to use. This instance of GpsSensor might also be shared among multiple objects, and Acme Corporation warned us that calling the calibrate method multiple times causes a crash.

To safely use GpsSensor, we can write a module whose sole responsibility is to create and configure this object upon request. In this way, any class can depend on GpsSensor without having to worry about its initialization.

Building objects with dependencies

Sometimes our modules must create objects that have dependencies. It is not the module’s responsibility to create these dependencies, or to go looking around for them. Like other classes, the module can also assume that these dependencies will be provided.

Let’s suppose, for example, that the class YahooWeather requires a WebSocket to work. The code for both of these classes is shown below.

Now that the constructor of YahooWeather requires a parameter, our YahooWeatherModule must be modified. We need to obtain, somehow, an instance of a WebSocket in order to invoke the constructor.

Instead of instantiating the dependency inside the module, which would lead to all the shortcomings of not using dependency injection, we can just modify the signature of the provider method, as shown below. Dagger will take care of finding or constructing a WebSocket for the module to use.

B uilding objects that must be configured

Sometimes a module needs to create an object that must be configured somehow, and this configuration might require a piece of information that only becomes available at running time.

Continuing our example, let’s suppose that the constructor of YahooWeather also requires an API key. Here is the modified class.

Note that we removed the Inject annotation. Since the construction of YahooWeather is being handled by our module, there is no need for Dagger to know about this constructor. Dagger would also not know how to inject a meaningful String parameter automatically (although this can also be done, as we will see in a future post).

If the API key is a constant available at compile time somewhere, say, in a class named BuildConfig, then a possible solution is the following:

Suppose, however, that the API key is only available at running time. For example, it could be a command-line argument. This information can be provided to the module, just like any other dependency, via its constructor.

This complicates things a little for Dagger, since now it has no idea how to construct such a module. When modules have no-argument constructors, then Dagger can easily instantiate and use them. In this case, however, we must instantiate the module ourselves and give it to Dagger. This can be done as follows, at the entry point for the application:

In lines 3–4 we grabbed the API key from the command-line arguments and instantiated the module ourselves. In lines 5–7, we asked Dagger to build a new component, using that instance of our module. Instead of calling the method create, we obtained a builder instead, passed the instantiated module, and then called build.

If there were more modules in our component that Dagger was unable to instantiate automatically, we would need to make one call to set each of these modules before calling build.

Conclusion

In today’s post we saw how to take more control over the creation of objects by using modules. The source code for this post can be found at GitHub.

In the next post, we will see how to specify that some dependencies can be shared among multiple objects. If you have any questions, comments or suggestions, please let me know.