Android dagger inject constructor

Dagger

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.

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.

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.

Читайте также:  Sketchup pro для android

For example, provideHeater() is invoked whenever a Heater is required:

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:

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:

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.

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.

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.

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:

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.

Now, our CoffeeApp can simply use the Dagger-generated implementation of CoffeeShop to get a fully-injected CoffeeMaker .

Now that the graph is constructed and the entry point is injected, we run our coffee maker app. Fun.

Bindings in the graph

The example above shows how to construct a component with some of the more typical bindings, but there are a variety of mechanisms for contributing bindings to the graph. The following are available as dependencies and may be used to generate a well-formed component:

  • Those declared by @Provides methods within a @Module referenced directly by @Component.modules or transitively via @Module.includes
  • Any type with an @Inject constructor that is unscoped or has a @Scope annotation that matches one of the component’s scopes
  • The component provision methods of the component dependencies
  • The component itself
  • Unqualified builders for any included subcomponent
  • Provider or Lazy wrappers for any of the above bindings
  • A Provider of a Lazy of any of the above bindings (e.g., Provider > )
  • A MembersInjector for any type

Singletons and Scoped Bindings

Annotate an @Provides method or injectable class with @Singleton . The graph will use a single instance of the value for all of its clients.

Читайте также:  One way heroics android

The @Singleton annotation on an injectable class also serves as documentation. It reminds potential maintainers that this class may be shared by multiple threads.

Since Dagger 2 associates scoped instances in the graph with instances of component implementations, the components themselves need to declare which scope they intend to represent. For example, it wouldn’t make any sense to have a @Singleton binding and a @RequestScoped binding in the same component because those scopes have different lifecycles and thus must live in components with different lifecycles. To declare that a component is associated with a given scope, simply apply the scope annotation to the component interface.

Components may have multiple scope annotations applied. This declares that they are all aliases to the same scope, and so that component may include scoped bindings with any of the scopes it declares.

Reusable scope

Sometimes you want to limit the number of times an @Inject -constructed class is instantiated or a @Provides method is called, but you don’t need to guarantee that the exact same instance is used during the lifetime of any particular component or subcomponent. This can be useful in environments such as Android, where allocations can be expensive.

For these bindings, you can apply @Reusable scope. @Reusable -scoped bindings, unlike other scopes, are not associated with any single component; instead, each component that actually uses the binding will cache the returned or instantiated object.

That means that if you install a module with a @Reusable binding in a component, but only a subcomponent actually uses the binding, then only that subcomponent will cache the binding’s object. If two subcomponents that do not share an ancestor each use the binding, each of them will cache its own object. If a component’s ancestor has already cached the object, the subcomponent will reuse it.

There is no guarantee that the component will call the binding only once, so applying @Reusable to bindings that return mutable objects, or objects where it’s important to refer to the same instance, is dangerous. It’s safe to use @Reusable for immutable objects that you would leave unscoped if you didn’t care how many times they were allocated.

Lazy injections

Sometimes you need an object to be instantiated lazily. For any binding T , you can create a Lazy which defers instantiation until the first call to Lazy ’s get() method. If T is a singleton, then Lazy will be the same instance for all injections within the ObjectGraph . Otherwise, each injection site will get its own Lazy instance. Regardless, subsequent calls to any given instance of Lazy will return the same underlying instance of T .

Provider injections

Sometimes you need multiple instances to be returned instead of just injecting a single value. While you have several options (Factories, Builders, etc.), one option is to inject a Provider instead of just T . A Provider invokes the binding logic for T each time .get() is called. If that binding logic is an @Inject constructor, a new instance will be created, but a @Provides method has no such guarantee.

Qualifiers

Sometimes the type alone is insufficient to identify a dependency. For example, a sophisticated coffee maker app may want separate heaters for the water and the hot plate.

In this case, we add a qualifier annotation. This is any annotation that itself has a @Qualifier annotation. Here’s the declaration of @Named , a qualifier annotation included in javax.inject :

Читайте также:  Лучший icq клиент для андроид

You can create your own qualifier annotations, or just use @Named . Apply qualifiers by annotating the field or parameter of interest. The type and qualifier annotation will both be used to identify the dependency.

Supply qualified values by annotating the corresponding @Provides method.

Dependencies may not have multiple qualifier annotations.

Optional bindings

If you want a binding to work even if some dependency is not bound in the component, you can add a @BindsOptionalOf method to a module:

That means that @Inject constructors and members and @Provides methods can depend on an Optional object. If there is a binding for CoffeeCozy in the component, the Optional will be present; if there is no binding for CoffeeCozy , the Optional will be absent.

Specifically, you can inject any of the following:

  • Optional (unless there is a @Nullable binding for CoffeeCozy ; see below)
  • Optional

(You could also inject a Provider or Lazy or Provider of Lazy of any of those, but that isn’t very useful.)

If there is a binding for CoffeeCozy , and that binding is @Nullable , then it is a compile-time error to inject Optional , because Optional cannot contain null . You can always inject the other forms, because Provider and Lazy can always return null from their get() methods.

An optional binding that is absent in one component can be present in a subcomponent if the subcomponent includes a binding for the underlying type.

You can use either Guava’s Optional or Java 8’s Optional .

Binding Instances

Often you have data available at the time you’re building the component. For example, suppose you have an application that uses command-line args; you might want to bind those args in your component.

Perhaps your app takes a single argument representing the user’s name that you’d like to inject as @UserName String . You can add a method annotated @BindsInstance to the component builder to allow that instance to be injected in the component.

Your app then might look like

In the above example, injecting @UserName String in the component will use the instance provided to the Builder when calling this method. Before building the component, all @BindsInstance methods must be called, passing a non-null value (with the exception of @Nullable bindings below).

If the parameter to a @BindsInstance method is marked @Nullable , then the binding will be considered “nullable” in the same way as a @Provides method is nullable: injection sites must also mark it @Nullable , and null is an acceptable value for the binding. Moreover, users of the Builder may omit calling the method, and the component will treat the instance as null .

@BindsInstance methods should be preferred to writing a @Module with constructor arguments and immediately providing those values.

Compile-time Validation

The Dagger annotation processor is strict and will cause a compiler error if any bindings are invalid or incomplete. For example, this module is installed in a component, which is missing a binding for Executor :

When compiling it, javac rejects the missing binding:

Fix the problem by adding an @Provides -annotated method for Executor to any of the modules in the component. While @Inject , @Module and @Provides annotations are validated individually, all validation of the relationship between bindings happens at the @Component level. Dagger 1 relied strictly on @Module -level validation (which may or may not have reflected runtime behavior), but Dagger 2 elides such validation (and the accompanying configuration parameters on @Module ) in favor of full graph validation.

Источник

Оцените статью