Exploring Dagger2

tura·2019년 4월 8일
0

Let's learn what is Dagger2 and how to use it.

Table of contents

Dependency Injection

"Your dependencies are going to come to you" - Jake Wharton

Dependency Injection let us get rid of dependencies from the business logic.
Here's a really simple example:

public class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
}

public class Main {
    private void test() {
        Engine engine = new ElectricEngine();
        Car car = new Car(engine);
    }
}

The engine of the Car are given from the outside of the class.
We separate the creation and the use of an object.

Boilerplate

Although we are injecting dependencies from the outside, we still need to manually create the instances and connect them. This would be annoying if the dependencies get more complex.

Dependency injection libraries, like Dagger2, usually provides some nice way to handle these problems. Let's get into it.

Some key concepts

Module

Specifies the providers. Providers are in charge of making and providing new objects.
Example of a simple module:

@Module
public class CarModule {
    @Provides
    public Engine provideEngine() {
        return new ElectricEngine();
    }
}

Methods annotated with @Providers mean they are providers.

Component

Components are essentially the glue that holds everythings together.
Example of a simple component:

@Component(modules = SimpleModule.class)
public interface SimpleComponent {
    void inject(SimpleUseCase useCase);
}

Dagger will use a component to generate the code for us.
In the example above, Dagger creates DaggerSimpleComponent for us.
This name comes from the name of the component - Dagger + {name of component}

Injection

It is possible to inject dependencies using @Inject annotation, like this:

public class SimpleUseCase {
    @Inject
    public SimpleData data;
}

In the codes above, data will be injected using SimpleModule#provideData

Summary

  • @Module and @Provides for providing dependencies
  • @Inject for requesting dependencies (in short, injection)
  • @Component for bridge between modules and injections

How it works (Simple)

Dependency graph

Usually classes are dependent to some other classes.
Here, the Car object needs an instance of Engine in order to be created:

@Module
public class CarModule {
    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine);
    }

    @Provides
    public Engine provideEngine() {
        return new ElectricEngine();
    }
}

Dagger creates a graph of dependencies when it parses the module.
It goes like this:

1. I need a car (@Inject)
2. Engine is needed to make a car
3. Create an engine
4. Create a car

It seems pretty simple, but if the dependencies are much more complex, it's really useful.

Advanced Topics

Various types of injections

Field Injection

It is the most common type of injection.

  • Fields should not be private or final.
  • Field injection happens after the constructor invoked.
  • It might be useful for Android, where there's no way to control the instantiation (e.g. Activities, Fragments)
public class User {
    @Inject
    CarMaker carMaker;
}

Method Injection

It is not that common type of injection, and is rarely used.

  • Method injection applys to the parameters of the method.
  • Method injection happens after the constructor invoked, obviously.
public class User {
    public void subscribeNewsLetter(@Inject News news) {
       news.subscribe(this)
    }
}

Constructor Injection

  • Construction injection happens before the construction of the object.
  • Construction injection applys to the constructor of the object.
public class EngineMaker {
}

public class CarMaker {
    @Inject
	public CarMaker(EngineMaker engineMaker) {
        
    }
}

public class User {
    @Inject
    CarMaker carMaker;
}

In the codes above, User requests CarMaker as a field.
If the constructor of CarMaker is not annotated with @Inject, you need to define provider function like this:

public class AppModule {
    @Provides
    public CarMaker provideCarMaker(EngineMaker engineMaker) {
        return new CarMaker();
    }
}

If you use constructor injection, you don't need to specify this.

Component methods depending on the types of injection

  • Field Injection and Method Injection are invoked after the instantiation
    Therefore, it is possible to define methods something like this:

    public void inject(User user);

    Internally Dagger creates a class prefixed with _MembersInjector for this.
    (In this case, User_MembersInjector)
    And DaggerAppComponent uses this class to inject requested dependencies.

    // generated code
    public void inject(User user) {
        injectUser(user);
    }
    
    private User injectUser(User instance) {
        User_MembersInjector.injectCarMaker(instance, getCarMaker());
        return instance;
    }
  • Constructor Injection are invoked before the instantiation
    Since there's no dependencies to inject, DaggerAppComponent does nothing to this line of code.

    public void inject(CarMaker carMaker);
    
    // generated code
    public void inject(CarMaker carMaker) {}

@Provides and @Binds

With @Provides, the method should return value corresponding to its return type.
@Binds only works on abstract methods, and

From the docs:

@Binds methods are a drop-in replacement for @Provides methods that simply return an injected parameter. Prefer @Binds because the generated implementation is likely to be more efficient.

So the following module declaration can be replaced with @Binds

@Provides
public Engine provideEngine() {
    return new ElectricEngine();
}

// Using @Binds
@Binds
public abstract Engine provideEngine(ElectricEngine engine);

Component

TBD

Scope

TBD

@Component.Builder and @BindsInstance

TBD

References

Here are some useful links about Dagger2.

0개의 댓글