[JAVA] Read the official Dagger2 documentation to understand the basics

Trigger

Dagger2 is also featured in Android Developers' Guide to App Architecture (https://developer.android.com/topic/libraries/architecture/guide.html). It's almost time to ** Android engineer who doesn't understand Dagger2 and DI **, so I looked it up. If you are not sure, please read the corresponding [Supplementary Note]. Since the source code is posted, it may be easy to imagine. I haven't touched on the recent update related to Android (dagger.android) this time. Bold what surprised you as a Dagger beginner.

It is almost a translation of the following document. Please point out what is wrong.

User's Guide https://google.github.io/dagger/users-guide.html

User's Guide

Dagger2 overview

As a good class for many apps, BarcodeDecoder will probably have class dependencies like BarcodeCameraFinder, DefaultPhysicsEngine, and HttpStreamer. In contrast, an example of a bad class is a class that takes up space without doing anything, such as BarcodeDecoderFactory. You can replace this with the dependency injection design pattern. Testing can be simplified by assembling using standard javax.inject annotations (JSR 330). There is no need for an implementation that replaces it with a class for hitting Fake's Web API. ** Dependency injection is not just for testing. You can make modules that are easy to reuse or replace. ** For example, the Authentication Module can be shared and used within the app, or the DevLoggingModule can be used for debugging and the ProdLoggingModule can be used in production.

Why is Dagger2 different from other DIs?

There have been many DI libraries so far. Why did you redevelop the wheels? Dagger2 is the first to be fully implemented by code generation. The guideline is to generate dependency injection that is as easy, traceable, and executable as possible.

Dependency declaration

Dagger Constructs an instance of your application's class and fills its dependencies. Use javax.inject.Inject annotation to identify constructors and fields. Use a constructor that uses the @Inject annotation when Dagger instantiates a class. When a new instance is needed, Dagger gets the required parameters and calls the constructor.

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger can also inject fields (member variables) directly. In this example, we will put an instance of the heater field and the pump field.

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;

  ...
}

If you have a field with ** @ Inject and no constructor with @ Inject, Dagger will create the field if requested, but will not try to create an instance. (Probably like this CoffeeMaker.) By creating a constructor with no arguments with @Inject, it will create an instance in the same way. ** ** Dagger also supports method injection, but it is commonly used in fields and constructors. Classes without @ Inject cannot be constructed with Dagger.

[Supplementary memo] Dependency declaration

It was a piece of code, so it's hard to understand, isn't it?

This is the image. (The code is here. Https://github.com/takahirom/dagger2-sample/commit/4b4e7a047dee0993735c6605bc06bdcd3084c8b8) As for the area around @Component, I will introduce it later, but create a Component and call maker (). ** What happens when you call maker () is that it just returns a new CoffeeMaker () and a new Heater () injected instance in that CoffeeMaker field. ** **

I am making a Component (described later)

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
    }

    @Component
    interface CoffeeShop {
        CoffeeMaker maker();
    }
}

@Inject allows the constructor to be executed from Dagger, and @Inejct specifies the field.

class CoffeeMaker {
    @Inject
    Heater heater;

    @Inject
    public CoffeeMaker() {
    }
}

A Heater is also created from the field.

class Heater {
    @Inject
    public Heater() {
    }
}

This time I used @Inject for the field (member variable), If you attach it to the argument of the constructor, the object of the argument will be new first and it will be used to call the constructor.

Satisfy dependencies

By default, Dagger fulfills the dependency by constructing the requested type. When you request a CoffeeMaker, new CoffeeMaker () sets the fields that can be injected. However, @Inject cannot be used everywhere.

In such cases, a method using @ Provides can satisfy the dependency. The return value of the method should be a type that satisfies the dependency.

For example, the provideHeader method is called when Heater is needed.

@Provides static Heater provideHeater() {
  return new ElectricHeater();
}

** Also, methods with @ Provides can have their own dependencies. Here is an example of returning Thermosiphon where Pump is needed. ** **

@Provides static Pump providePump(Thermosiphon pump) {
  return pump;
}

Methods with @ Provide must belong to a module. Just annotate the class with @Module.

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater() {
    return new ElectricHeater();
  }

  @Provides static Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

** By convention, the @ Provides method should have a method name that starts with provide, and the module should have a class name that ends with Module. ** **

[Supplementary memo] Satisfy the dependency

I will summarize it in the direction of building the following relationship (Graph).

Build a relationship (Graph)

Classes with @ Injection and @ Provides form a relationship (Graph) constructed by dependencies. Access the association (Graph) through a "set of well-defined association routes" with a Calling code such as the application's main () method or the Android Application class. In Dagger2 the set is declared ** on an interface where the desired class has a method with no return arguments. ** Adapt the @Component annotation and pass the module to the annotation's modules to create a complete implementation using that module.

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
  CoffeeMaker maker();
}

A class for the Dagger and the first Component will be generated from which you can create an instance of CoffeeShop.

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();

If it is an inner class, it will be generated with _ delimiter like this

In the following cases, it will be named DaggerFoo_Bar_BazComponent.

class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

This dripCoffeeModule () is not needed if the DripCoffeeModule class has access to (and does not need to be) the default constructor. Also, if all the methods with @ Provides are static, the constructor does not need to be accessible.

DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())

If you don't need an instance of the module, you can use the create () method as follows instead of using the builder.

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

Now you can simply use the CoffeeShop with an implementation that allows Dagger to get a CoffeeMaker with full dependencies.

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

[Supplementary memo] Satisfy the dependency

Since it was a fragment of the code this time as well, there may have been some parts that were difficult to understand, so I will post all the code. (Here is the code. Https://github.com/takahirom/dagger2-sample/commit/7adcffb7b4b38d1aede06645df9bbdc37603bd23)

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
    }

    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();
    }

    @Module
    static class DripCoffeeModule {
        @Provides static Heater provideHeater() {
            return new ElectricHeater();
        }

        @Provides static Pump providePump(Thermosiphon pump) {
            return pump;
        }
    }

}
class CoffeeMaker {
    @Inject
    Heater heater;
    @Inject
    Pump pump;

    @Inject
    public CoffeeMaker() {
    }
}

Currently, there are empty @Inject-only classes such as Heater, Pump, ElectricHeater, and Thermosiphon.

public class ElectricHeater extends Heater {
    @Inject
    public ElectricHeater(){

    }
}

By using @Module, you can see that each instance is used. image.png

Graph binding

This example shows an example of building a component using typical bindings, but there are various mechanisms to help with the association (Graph) binding. The following can be used when building dependencies and are used to create components in good shape.

(There seems to be the following, but I don't understand everything)

Singleton and Scoped Bindings

@Singleton is attached to the method with @ Provides or the injectable class. Graph Use one instance for all users.

@Provides @Singleton static Heater provideHeater() {
  return new ElectricHeater();
}

The @ Singleton to the class is also useful as a document. You can see that this class can be shared by multiple threads.

@Singleton
class CoffeeMaker {
  ...
}

** Dagger2 connects the scoped (including @ Singleton) instance in the Graph with the instance of the component's implementation **, so you need to scope the component. For example, it doesn't make sense to have the same component have a @ Singleton binding (which seems to mean a binding to an object in the Graph) and a @ RequestScoped (custom-defined Scope) binding at the same time. Because it must be in a component with a different life cycle. To create a custom-defined Scope, just apply the annotations to the Component.

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

[Supplementary Note] Singleton and Scoped Bindings

How are Singleton instances managed? (Here is the code https://github.com/takahirom/dagger2-sample/commit/063b7f45c9fb97e54b90ce56a4f96b217e39a6bc )

Dagger2 connects the scoped (including @ Singleton) instance in the Graph with the instance of the component implementation. What does that mean? How is it managed?

As a simple example, I experimented with @Singleton.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final CoffeeShop coffeeShop =  DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
        System.out.println(coffeeShop.maker());
        final CoffeeShop coffeeShop2 = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop2.maker());
        System.out.println(coffeeShop2.maker());

    }

    @Singleton
    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();
    }

    
}
@Singleton
class CoffeeMaker {
    @Inject
    Heater heater;
    @Inject
    Pump pump;

    @Inject
    public CoffeeMaker() {
        System.out.println("new CoffeeMaker()");
    }
}

This will output the following: In other words, @ Singleton seems to be associated with CoffeeShop.

new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d

I tried to dump the memory in this state. In this implementation, when MainActivity.onCreate () ends, the reference to CoffeeMaker disappears, that is, the instance disappears.

image.png

What if I have an instance of CoffeeShop in Actviity?

public class MainActivity extends AppCompatActivity {

    private CoffeeShop coffeeShop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coffeeShop = DaggerMainActivity_CoffeeShop.create();
        System.out.println(coffeeShop.maker());
        System.out.println(coffeeShop.maker());
    }

It seems that the instance is held by the coffeeMakerProvider in the field of the DaggerMainActivity_CoffeeShop class automatically generated by Dagger which implements the interface of CoffeeShop.

image.png

** Dagger2 somehow made sense of ** connecting a scoped (including @ Singleton) instance in the Graph with an instance of the component's implementation. I also experimented without @Singleton, but the instance of CoffeeMaker disappeared normally.

Reusable scope

Sometimes you want to reduce the number of instantiations, but sometimes you don't have to limit it to one. Instantiation is an expensive process on Android, for example. In such cases, you can use @Reusable. @Reusable, unlike other scopes, does not bind to one component. Instead, the component returns a cached instance or instantiates it. ... (It seems to be a function that is not used much, so ** I will omit it this time **)

Releasable references

... (It seems to be implemented like this, but it seems that it is not used so much, so ** I will omit it this time **)

@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;

void lowMemory() {
  myScopeReferences.releaseStrongReferences();
}

...

Lazy injections Sometimes you need to delay instantiating. You can delay instantiation until you call the get () method of Lazy for any binding T. If T is a singleton, all instances in ObjectGraph will be the same. If it is not a singleton, it will be instantiated at each injection point. But the second and subsequent times it returns the same instance.

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}

Provider injections You may need multiple instances instead of injecting one value. There are several options, such as using Factory or Builder, but one option is to use Provider instead of just a T type. Provider calls the binding logic each time the get () method is called. If the binding logic is a constructor with @Inject, a new instance will be created, but if the instance is created by a method with @ Provides, there is no such guarantee.

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //new filter every time.
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

Note: Code that injects Provider can create confusing code that can result in misscoped objects, misscoped or misstructured objects being used in the Graph. In many cases, you can take advantage of factories and Lazy , or reconfigure the lifetime and structure of your code to inject T. In some cases Provider can be implemented efficiently. A common use is when you need to take advantage of a legacy architecture that doesn't fit the object's natural lifetime.

(** It was also used in the Architecture Component sample app ** https://github.com/googlesamples/android-architecture-components/blob/e33782ba54ebe87f7e21e03542230695bc893818/GithubBrowserSample/app/src/main/java/com/android/ example / github / viewmodel / GithubViewModelFactory.java # L30)

Qualifiers Sometimes types alone are not enough to identify dependencies. For example, in a sophisticated coffee maker, I would like to separate the heater for water and the heater for hot plate. In this case, add a qualifier annotation. This is an annotation with @ Qualifier. The declaration of @ Named of the modifier annotation included in javax.inject is as follows.

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

You can create your own qualifier annotation or use @Named. It can be limited by applying annotations to fields or arguments. Types and modifier annotations are used to identify dependencies.

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

Provides the value specified by annotating the corresponding @Provides method.

@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

@Provides @Named("water") static Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

Dependencies may not have multiple qualifier annotations. (?)

[Supplementary memo] Qualifiers

I actually tried using it and tried to see what would happen if I entered a different name in named. (Here is the code https://github.com/takahirom/dagger2-sample/commit/fbabf72a454ec7a96628783a81e12bcb765d6cad)

    @Module
    static class DripCoffeeModule {
        @Provides
        @Named("water")
        static Heater provideHeater() {
            return new ElectricHeater();
        }
class CoffeeMaker {
    @Inject
    @Named("hot plate")
    Heater heater;
...
}

It detects it as an error and warns me at compile time.

MainActivity.java:30:error: @javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater cannot be provided without an @Provides- or @Produces-annotated method.
        CoffeeMaker maker();
                    ^
      @javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater is injected at
          com.github.takahirom.dagger_simple.CoffeeMaker.heater
      com.github.takahirom.dagger_simple.CoffeeMaker is provided at
          com.github.takahirom.dagger_simple.MainActivity.CoffeeShop.maker()
1 error

You can compile it if you make it water properly as shown below.

class CoffeeMaker {
    @Inject
    @Named("water")
    Heater heater;

Optional bindings ... (It seems to be a function when using Optional and Dagger of Java 8 or Guava, so I will omit it.)

Binding Instances Data is often available when building components. For example, consider an application that uses command line arguments. You may want to bind those arguments within a component. Suppose your application takes an argument (String) that represents the name of the user you want to inject with the annotation @ UserName. You can add @BindsInstance to the Component Builder so that you can inject that instance into the Component Builder.

@Component(modules = AppModule.class)
interface AppComponent {
  App app();

  @Component.Builder
  interface Builder {
    @BindsInstance Builder userName(@UserName String userName);
    AppComponent build();
  }
}

And the app looks like this.

public static void main(String[] args) {
  if (args.length > 1) { exit(1); }
  App app = DaggerAppComponent
      .builder()
      .userName(args[0])
      .build()
      .app();
  app.run();
}

In the above example, if you inject the @ UserName String into the component, the instance provided to the builder is used when calling this method. (? I didn't know where this method is ...) You need to call all the @BindsInstance methods before building the component. The following @Nullable bindings are an exception. If the parameter of the @BindsInstance method is marked @Nullable, the binding is considered "nullable" in the same way as the @ Provides method. Therefore, the injecter is also @Nullable and null must be accepted. You can also omit the Builder method call, where the component treats the instance as null. ** The @BindsInstance method should be used in preference to passing it to @ Module in the constructor and adapting it immediately. ** **

(This is also a sample of Architecture Components https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/di/AppComponent. java # L38 seems to be used.)

[Supplementary Note] Binding Instances

It's almost the same, but let's take a look at the code (the code is here https://github.com/takahirom/dagger2-sample/commit/c2cbfdbbf883203378ad51fd1d629ea25a7dbe6f)

Qualifier seems to be good for the one that was UserName in the example. So I use it at @Named. This time I tried to pass whether to use sugar when starting the application.

Suger (true) specifies to use sugar (I add sugar)

        coffeeShop = DaggerMainActivity_CoffeeShop.builder().sugar(true).build();
        System.out.println(coffeeShop.maker());

It's the same as the example.

    @Singleton
    @Component(modules = DripCoffeeModule.class)
    interface CoffeeShop {
        CoffeeMaker maker();

        @Component.Builder
        interface Builder {
            @BindsInstance
            Builder sugar(@Named("sugar") boolean isAddingSugger);
            CoffeeShop build();
        }
    }

Put the suger where you want it.

class CoffeeMaker {
...
    @Inject
    @Named("sugar")
    boolean isAddSugar;

Validation at compile time

... (Dagger's annotation processor is strict and will give an error if wrong, so I'll omit it.)

Code generation at compile time

Dagger's annotation processor automatically generates classes such as CoffeeMaker_Factory.java and CoffeeMaker_MembersInjector.java. Those classes are the details of the Dagger implementation. This is useful when debugging through injections, but you don't have to take advantage of them directly. ** All you need in your code is one that starts with a Dagger for the component. ** **

How to use Dagger in build

... (I will omit it because it is Android after all.)

Summary

I felt that it would be convenient if I could master it. In particular, if you create an instance of a certain class with Dagger, is it a chain of creating instances such as creating arguments for that field or method? It was very convenient because I was going to do it. However, at first I feel that the learning cost is a little heavy, so I hope there are people who will be a little easier with this Qiita.

Recommended Posts

Read the official Dagger2 documentation to understand the basics
Understand the basics of docker
[java8] To understand the Stream API
Understand the basics of Android Audio Record
Java reference to understand in the figure
[Ruby] From the basics to the inject method
Why was the code painful to read?
[Ruby basics] How to use the slice method
[For beginners] Quickly understand the basics of Java 8 Lambda
Understand the characteristics of Scala in 5 minutes (Introduction to Scala)
Java classes and instances to understand in the figure