Dagger is a dependency injection framework whose core implementation principle is to generate dependency injection code at compile time. We can use the annotations provided by Dagger to describe our dependency injection requirements.

In order to implement dependency injection,Dagger needs to know how the object is created and the developer needs to know how to get the object created by Dagger. This article will describe the functionality of the Dagger annotations and how they are implemented.

Dependency injection: a method of passing in a dependency (object) instead of initializing it.

Dagger basis

@Inject

Dagger can be used to specify both the dependency of the object and how the dependent object is created. Each usage of the Dagger generates a different secondary class at compile time to complete the injection of the dependent instance:

Declared on a member variable

class StudentTest {
    @Inject
    lateinit var nameInfo: NameInfo
}
Copy the code

Dagger generates the corresponding dependency injection class (StudentTest_MembersInjector) at compile time, which is used at run time to inject the nameInfo instance of the StudentTest object:

public final class StudentTest_MembersInjector implements MembersInjector<StudentTest> {
  private final Provider<NameInfo> nameInfoProvider;

  public StudentTest_MembersInjector(Provider<NameInfo> nameInfoProvider) {
    this.nameInfoProvider = nameInfoProvider;
  }

  public static MembersInjector<StudentTest> create(Provider<NameInfo> nameInfoProvider) {
    return new StudentTest_MembersInjector(nameInfoProvider);}

  @Override
  public void injectMembers(StudentTest instance) {
    injectNameInfo(instance, nameInfoProvider.get());
  }

  public static void injectNameInfo(StudentTest instance, NameInfo nameInfo) {
    instance.nameInfo = nameInfo;
  }
}
Copy the code

Provider

Creates the template interface for NameInfo

Declaration on the constructor

class Student @Inject constructor(val nameInfo: NameInfo) : IPeople
Copy the code

Dagger generates the Student_Factory class at compile time, which relies on the construct argument to construct the Student object:

public final class Student_Factory implements Factory<Student> { private final Provider<NameInfo> nameInfoProvider; . public static Student_Factory create(Provider<NameInfo> nameInfoProvider) {return new Student_Factory(nameInfoProvider);
  }

  public static Student newInstance(NameInfo nameInfo) {
    returnnew Student(nameInfo); }}Copy the code

If the construct parameter is marked with @inject, Dagger looks for the XX_Factory of the parameter, creates the parameter object, and then creates the current object

@Module

It encapsulates the method used to create an instance of an object:

Inject the way scattered everywhere is not easy to manage

@Module
class StudentModule {
    @Provides
    fun provideNameInfo() = NameInfo("wang"."pengcheng")}Copy the code

@Provides needs to be declared in the @Module annotation class, which specifies how the dependent instance is created. For each @Provides annotation method, the Dagger generates the corresponding Factory at compile time:

StudentModule_ProvideNameInfoFactory

public final class StudentModule_ProvideNameInfoFactory implements Factory<NameInfo> { private final StudentModule module; . public static NameInfo provideNameInfo(StudentModule instance) {return Preconditions.checkNotNull(instance.provideNameInfo(), "Cannot return null from a non-@Nullable @Provides method"); }}Copy the code

Create the corresponding NameInfo instance using StudentModule().providenameInfo ().

@Component

Manage dependency instances, link @inject and @Module, Inject dependency instances into objects:

@Component(modules = [StudentModule::class])
interface StudentComponent {
    fun inject(studentTest: StudentTest)
}
Copy the code

StudentComponent collects the dependency creation methods in modules = [StudentModule::class] and uses these methods to create object instances and assign values to member variables required by StudentTest.

Dagger generates the corresponding implementation class DaggerStudentComponent for this interface at compile time. This class implements dependency injection for StudentTest:

public final class DaggerStudentComponent implements StudentComponent {
  private final StudentModule studentModule;

  private DaggerStudentComponent(StudentModule studentModuleParam) {
    this.studentModule = studentModuleParam;
  }

  ...
  
  @Override
  public void inject(StudentTest studentTest) {
    injectStudentTest(studentTest);}

  @Override
  public NameInfo provideNameInfo() {
    returnStudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule); } private StudentTest injectStudentTest(StudentTest instance) { StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule));returninstance; } public static final class Builder { ... }}Copy the code

The DaggerStudentComponent private constructor is created by passing in the StudentModule object as the Builder

Exposed dependency instances

We can add methods to @Component to expose dependent instances:

@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {
    ...
    fun getTeacher():Teacher
}
Copy the code

Dagger generates a factory method to create a Teacher instance, which can be retrieved directly from the code:

val teacher = DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().getTeacher()
Copy the code

Simple use of Dagger

Through the introduction of the above three daggers, we have learned the basic usage and implementation principle of Dagger, which can be used in the following way when coding:

class StudentTest {
    @Inject
    lateinit var nameInfo: NameInfo

    constructor() {
        DaggerStudentComponent.builder().studentModule(StudentModule()).build().inject(this)
        Log.d("dagger-test"."studentName : ${nameInfo.first} ${nameInfo.last}")}}Copy the code

Logcat output:

 D/dagger-test: wang pengcheng
Copy the code

@Binds

It can also specify how a dependent instance is provided like @provides, but it can only be declared in the abstract method, which tells the Dagger interface which implementation to use:

@Module(includes = [StudentModule::class])
abstract class ClassroomModule {
    @Binds
    abstract fun bindPeopleWithStudent(test: Student): IPeople
}
Copy the code

@Module(includes = [StudentModule::class]) enables ClassroomModule to create dependent instances for StudentModule

Fun bindPeopleWithStudent(test: Student): IPeople defines the implementation class for IPeople as Student

Provide the Student instance with @provides or Inject annotation in the constructor:

class Student @Inject constructor(val nameInfo: NameInfo) : IPeople
Copy the code

@Sharing addresses the need for interface oriented programming, which specifies the implementation classes that depend on the interface. This is also possible, of course, with @Provides (the method entity is a strong transformation of type), but @Binds is much clearer:

  @Provides
  fun providePeopleWithStudent() = Student(provideNameInfo()) as IPeople
Copy the code

Component dependence

If ClassroomComponent needs to use a dependent instance of StudentComponent, you can write:

@Component(modules = [ClassroomModule::class], dependencies = [StudentComponent::class])
interface ClassroomComponent {
    fun inject(test: ClassroomTest) } @Component(modules = [StudentModule::class]) interface StudentComponent { fun provideNameInfo(): NameInfo // pass to the dependent Component}Copy the code

StudentComponent provides NameInfo for StudentModule. ClassroomComponent provides NameInfo for dependencies = [StudentComponent::class]. In addition to dependencies = [StudentComponent::class], you need to expose the dependency instance method Fun provideNameInfo(): NameInfo

The above Dagger will generate:

public final class DaggerClassroomComponent implements TestComponent {
  private final StudentComponent studentComponent;
  
  private ClassroomTest injectClassroomTest(ClassroomTest instance) {
    ClassroomTest_MembersInjector.injectStudent(instance, getStudent());
    return instance;
  }

}

public final class DaggerStudentComponent implements StudentComponent {
  @Override
  public NameInfo provideNameInfo() {
    returnStudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule); }}Copy the code

StudentComponent becomes a member variable of the DaggerTestComponent, so you can inject NameInfo dependencies for Test:

class ClassroomTest {
    @Inject
    lateinit var student: IPeople

    constructor() { DaggerClassroomComponent.builder().studentComponent(DaggerStudentComponent.builder().studentModule(StudentModule()).buil d()).build().inject(this) Log.d("dagger-test"."studentName : ${student.getName()}")}}Copy the code

Subcomponent

< span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;

@SubcomponentYou can makeBComponentintoAComponentThe inner class is then usedAComponentDependency injection capability (@Module) :

AComponent:

@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {

    fun inject(test: ClassroomTest) fun studentComponent(): studentComponent.Builder // Use to construct studentComponent}Copy the code
@Module(subcomponents = [StudentComponent::class])
class ClassroomModule {
    @Provides
    fun provideTeacher() = Teacher()
}
Copy the code

Subcomponents = [StudentComponent::class] Indicates StudentComponent. You can see the dependency instances provided by ClassroomModule:

BComponent:

@Subcomponent(modules = [StudentModule::class]) interface StudentComponent { fun inject(studentTest: @subComponent. Builder Interface Builder {fun build(): StudentTest}}Copy the code

use@SubcomponentStatement of the childComponentYou also need to display the declarationBuilderSo that the parent knows how to create the child

Dagger does not generate DaggerStudentComponent, only DaggerClassroomComponent:

public final class DaggerClassroomComponent implements ClassroomComponent {

  private final class StudentComponentBuilder implements StudentComponent.Builder {
    @Override
    public StudentComponent build() {
      returnnew StudentComponentImpl(new StudentModule()); } } private final class StudentComponentImpl implements StudentComponent { private final StudentModule studentModule; private StudentComponentImpl(StudentModule studentModuleParam) { this.studentModule = studentModuleParam; } @Override public void inject(StudentTest studentTest) { injectStudentTest(studentTest); } private StudentTest injectStudentTest(StudentTest instance) { StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule)); StudentTest_MembersInjector.injectTeacher(instance, ClassroomModule_ProvideTeacherFactory.provideTeacher(DaggerClassroomComponent.this.classroomModule));returninstance; }}}Copy the code

You can see that the StudentTest injectStudentTest(StudentTest instance) method uses the dependent instance method provided by ClassroomModule.

Since DaggerStudentComponent is not generated, the build for DaggerStudentComponent must do this:

class StudentTest {
    
    @Inject lateinit var nameInfo: NameInfo
    
    @Inject lateinit var teacher: Teacher

    constructor() { DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().studentComponentBuilder().build().inject(t his) Log.d("dagger-test"."teacher name : ${teacher.name}")}}Copy the code

@Scope

@scope has a Dagger tied to @Component: If an @Module provides a dependency instance that declares the same @scope as @Component, then the @Component uses the same dependency instance for dependency injection:

@Singleton
@Component(modules = [SingletonModule::class])
interface AppComponent : AndroidInjector<TestApplication> {
    fun inject(app: Application)

    fun getClassroom():Classroom
}
Copy the code

You can also get the singleton Classroom directly by hand

@Module
class SingletonModule {
    @Singleton
    @Provides
    fun provideClassRoom() = Classroom()
}
Copy the code

The @singleton is the @scope built into the Dagger that defines a unique dependency instance within the @Component

class Test {
    ...
    @Inject lateinit var room1: Classroom
    @Inject lateinit var room2: Classroom
    ...    
}
Copy the code

The two member variables of Test refer to the same Classroom instance, but when using @Scope it is important to note that @SubComponent cannot declare the same @Scope as @Component

The implementation principle of singleton

Take a look at the Component injection implementation:

private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
  ClassroomActivity_MembersInjector.injectTeacher1(instance, provideTeacherProvider.get());
  ClassroomActivity_MembersInjector.injectTeacher2(instance, provideTeacherProvider.get());
  return instance;
}
Copy the code

Objects obtained using the same Provider

Dagger in Android

The basic principle of Dagger and how to use it are described above, but how to use Dagger in Android?

Using the Dagger with the basic usage above causes some column problems. For example, the Activity/Fragment is created by the system, so we cannot turn it into a dependency instance or do automatic dependency injection, so we need to write code like the following:

class ClassroomActivity : Activity() { @Inject lateinit var teacher: Teacher override fun onCreate(savedInstanceState: Bundle?) { ClassroomActivityComponent.builder().build().inject(this); }}Copy the code

There are two bad things about writing this way:

  1. Too much code like this
  2. eachActivity/FragmentIn the injection is all need to know its correspondingDaggerXXXComponent

How do you solve it? Dagger officially gives the following implementation steps:

Automatic injection of activities

  1. The top-level Component is bound to the AndroidInjectionModule

  2. AndroidInjector

    Defines an @SubComponent that inherits from AndroidInjector

@Subcomponent(modules = [ClassroomModule::class])
interface ClassroomActivitySubcomponent : AndroidInjector<ClassroomActivity> {

    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<ClassroomActivity>

}
Copy the code
  1. To define a@ModuleAnd bind the previously defined@Subcomponent
@Module(subcomponents = [ClassroomActivitySubcomponent::class]) abstract class ClassroomActivityModule { @Binds @IntoMap  @ClassKey(ClassroomActivity::class) abstract funbindClassroomActivityFactory(factory: ClassroomActivitySubcomponent.Factory): AndroidInjector.Factory<*>

}
Copy the code
  1. Let’s take the above definition@ModuleBound to the@Component
@Component(modules = [AndroidInjectionModule::class, ClassroomActivityModule::class])
interface AppComponent : AndroidInjector<TestApplication> {

    fun inject(app: Application)

}
Copy the code
  1. Application initializationDispatchingAndroidInjector
class TestApplication : Application(), HasAndroidInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }

    override fun androidInjector() = dispatchingAndroidInjector
}
Copy the code
  1. Inject dependencies into your Activity
class ClassroomActivity : Activity() {

    @Inject
    lateinit var teacher: Teacher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        AndroidInjection.inject(this)
        setContentView(TextView(this).apply {
            text = "${teacher.nameInfo.getName()} "}}})Copy the code

If you don’t want to write AndroidInject.inject (this) every time, you can simply have your Activity inherit from DaggerAcitivity

Inject (this) automatically finds dependent instances in the AppComponent and injects them into ClassroomActivity.

For the above two steps 2, 3, actually can use @ ContributesAndroidInjector to step:

@Module
abstract class ClassroomActivityModule {

    @ContributesAndroidInjector(modules = [ClassroomModule::class])
    abstract fun contributeInjectorClassroomActivity(): ClassroomActivity

}
Copy the code

Dagger at compile time according to @ ContributesAndroidInjector automatically generated step 2 and 3 of the code above.

So how does that work?

The principle of automatic Activity injection

Take a look at the dependency injection code generated in DaggerAppComponent:

public final class DaggerAppComponent implements AppComponent { ... private Map<Class<? >, Provider<AndroidInjector.Factory<? >>>getMapOfClassOfAndProviderOfAndroidInjectorFactoryOf() {
    returnCollections.<Class<? >, Provider<AndroidInjector.Factory<? >>>singletonMap(ClassroomActivity.class, (Provider) classroomActivitySubcomponentFactoryProvider); } @SuppressWarnings("unchecked")
  private void initialize() {
    this.classroomActivitySubcomponentFactoryProvider = new Provider<ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory>() {
      @Override
      public ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory get() {return new ClassroomActivitySubcomponentFactory();}
    };
  }
  
  ...

  private final class ClassroomActivitySubcomponentImpl implements ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent {
    
    ...

    @Override
    public void inject(ClassroomActivity arg0) {injectClassroomActivity(arg0);}

    private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
      ClassroomActivity_MembersInjector.injectTeacher(instance, getTeacher());
      returninstance; }}}Copy the code

The logic above has two core points:

  1. ClassroomActivityIs the use ofClassroomActivitySubcomponentImplTo complete dependency injection
  2. ClassroomActivitySubcomponentImplStored in theDaggerAppComponent.getMapOfClassOfAndProviderOfAndroidInjectorFactoryOfThe map of

Then continue to look at what happens to AndroidInjection. Inject (this) :

public final class AndroidInjection { public static void inject(Activity activity) { Application application = activity.getApplication(); . injector = ((HasAndroidInjector) application).androidInjector(); . injector.inject(activity); }}Copy the code

That it ended up call activity. GetApplication () androidInjector (). The injector, inject (activity), is called the DispatchingAndroidInjector < Any > inj Ect (), which will eventually call:

public boolean maybeInject(T instance) { Provider<AndroidInjector.Factory<? >> factoryProvider = injectorFactories.get(instance.getClass().getName()); AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get(); . factory.create(instance).inject(instance); }Copy the code

Actually injectorFactories above is the Factory in the DaggerAppComponent Map, the final call to ClassroomActivitySubcomponentImpl. Inject ()

Reference documentation

Developer.android.com/training/de…

Dagger. Dev/dev – guide/a…