The best classes in any application are the ones that do stuff: the BarcodeDecoder, the KoopaPhysicsEngine, and the AudioStreamer. These classes have dependencies; perhaps a BarcodeCameraFinder, DefaultPhysicsEngine, and an HttpStreamer.
To contrast, the worst classes in any application are the ones that take up space without doing much at all: the BarcodeDecoderFactory, the CameraServiceLoader, and the MutableContextWrapper. These classes are the clumsy duct tape that wires the interesting stuff together.
Dagger is a replacement for these FactoryFactory classes that implements the dependency injection design pattern without the burden of writing the boilerplate. It allows you to focus on the interesting classes. Declare dependencies, specify how to satisfy them, and ship your app.
By building on standard javax.inject annotations (JSR 330), each class is easy to test. You don’t need a bunch of boilerplate just to swap the RpcCreditCardService out for a FakeCreditCardService.
Dependency injection isn’t just for testing. It also makes it easy to create reusable, interchangeable modules. You can share the same AuthenticationModule across all of your apps. And you can run DevLoggingModule during development and ProdLoggingModule in production to get the right behavior in each situation.
Why Dagger 2 is Different
Dependency injection frameworks have existed for years with a whole variety of APIs for configuring and injecting. So, why reinvent the wheel? Dagger 2 is the first to implement the full stack with generated code. The guiding principle is to generate code that mimics the code that a user might have hand-written to ensure that dependency injection is as simple, traceable and performant as it can be. For more background on the design, watch this talk (slides) by Gregory Kick.
Using Dagger
We’ll demonstrate dependency injection and Dagger by building a coffee maker. For complete sample code that you can compile and run, see Dagger’s coffee example.
Declaring Dependencies
Dagger constructs instances of your application classes and satisfies their dependencies. It uses the javax.inject.Inject annotation to identify which constructors and fields it is interested in.
Use @Inject to annotate the constructor that Dagger should use to create instances of a class. When a new instance is requested, Dagger will obtain the required parameters values and invoke this constructor.
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
…
}
Dagger can inject fields directly. In this example it obtains a Heater instance for the heater field and a Pump instance for the pump field.
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
…
}
If your class has @Inject-annotated fields but no @Inject-annotated constructor, Dagger will inject those fields if requested, but will not create new instances. Add a no-argument constructor with the @Inject annotation to indicate that Dagger may create instances as well.
Dagger also supports method injection, though constructor or field injection are typically preferred.
Classes that lack @Inject annotations cannot be constructed by Dagger.
Satisfying Dependencies
By default, Dagger satisfies each dependency by constructing an instance of the requested type as described above. When you request a CoffeeMaker, it’ll obtain one by calling new CoffeeMaker() and setting its injectable fields.
But @Inject doesn’t work everywhere:
Interfaces can’t be constructed.
Third-party classes can’t be annotated.
Configurable objects must be configured!
For these cases where @Inject is insufficient or awkward, use an @Provides-annotated method to satisfy a dependency. The method’s return type defines which dependency it satisfies.
For example, provideHeater() is invoked whenever a Heater is required:
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
It’s also possible for @Provides methods to have dependencies of their own. For example, since ElectricHeater has an @Inject constructor, the above method could be written instead as:
@Provides static Heater provideHeater(ElectricHeater heater) {
return heater;
}
This way Dagger takes care of instantiating ElectricHeater, and the @Provides method is only used to alias it to the type Heater.
In this particular case, we can simplify things further using an @Binds method to define the alias. Unlike @Provides, an @Binds method is abstract, and has no implementation:
@Binds Heater bindHeater(ElectricHeater impl);
Note: Using @Binds is the preferred way to define an alias because Dagger only needs the module at compile time, and can avoid class loading the module at runtime.
Finally, all @Provides methods must belong to a module. These are just classes that have an @Module annotation.
@Module
interface HeaterModule {
@Binds Heater bindHeater(ElectricHeater impl);
}
By convention, @Provides methods are named with a provide prefix, @Binds methods are named with bind prefix and module classes are named with a Module suffix.
Building the Graph
The @Inject and @Provides-annotated classes form a graph of objects, linked by their dependencies. Calling code like an application’s main method or an Android Application accesses that graph via a well-defined set of roots. In Dagger 2, that set is defined by an interface with methods that have no arguments and return the desired type. By applying the @Component annotation to such an interface and passing the module types to the modules parameter, Dagger 2 then fully generates an implementation of that contract.
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
The implementation has the same name as the interface prefixed with Dagger. Obtain an instance by invoking the builder() method on that implementation and use the returned builder to set dependencies and build() a new instance.
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
Note: If your @Component is not a top-level type, the generated component’s name will include its enclosing types’ names, joined with an underscore. For example, this code:
class Foo {
static class Bar {
@Component
interface BazComponent {}
}
}
would generate a component named DaggerFoo_Bar_BazComponent.
Any module with an accessible default constructor can be elided as the builder will construct an instance automatically if none is set. And for any module whose @Provides methods are all static, the implementation doesn’t need an instance at all. If all dependencies can be constructed without the user creating a dependency instance, then the generated implementation will also have a create() method that can be used to get a new instance without having to deal with the builder.
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
Now, our CoffeeApp can simply use the Dagger-generated implementation of CoffeeShop to get a fully-injected CoffeeMaker.
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
Now that the graph is constructed and the entry point is injected, we run our coffee maker app. Fun.