Full record of kotlin using Dagger2

  • 2021-08-28 21:11:54
  • OfStack

Preface

As a dependency injection artifact, Dagger2 is believed to be known to many friends. However, some of its concepts are not so clear to understand, and in the process of using them, they are also confused.

Dagger2 has a dependency injection framework based on JSR-330 standard developed by Google, which will automatically generate relevant codes during compilation, and is responsible for creating dependent objects to achieve decoupling purposes.

The following will introduce in detail the related contents about kotlin using Dagger2, and share it for your reference and study. The following words will not be said much. Let's take a look at the detailed introduction.

Configuring Dagger2 in kotlin

Configure the following in the build. gradle file of the app module for knowledge about kapt.


apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
 ...
}
dependencies {
 ...
 implementation 'com.google.dagger:dagger:2.11'
 kapt 'com.google.dagger:dagger-compiler:2.11'
}

Related common notes:

@Inject @Component @Module @Provides @ Qualifier and @ Named @ Scope and @ Singleton

@Inject

The @ Inject annotation is only the annotation defined in JSR-330, in the javax. inject package. The annotation itself is not useful; It depends on the injection framework to be meaningful and can be used to mark constructors, properties, and methods.

Tag constructor

A marked constructor can have 0 or more dependencies as parameters.

You can mark up to one constructor in the same class.


class People @Inject constructor(val name:String = "Tom")

Note that this writing is not allowed in kotlin because it is equivalent to multiple constructions in java People(String name), People() The correct writing should be as follows:


data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
}

Tag attribute

The marked attribute cannot be of final or val in kotlin.

Injected attributes cannot be decorated with private (not supported by Dagger2, not @ Inject).


 @Inject
 lateinit var people:People

Labeling method

A marked method can have 0 or more dependencies as parameters.

Methods cannot be abstract.


class HomeActivity : AppCompatActivity() {
 private lateinit var people:People
 @Inject
 fun setPeople(people:People){
 this.people = people
 }
}

There is no essential difference between this method injection and attribute injection, and the implementation effect is basically 1. Another way is for the @ Inject tag to be injected into a method of the class, which is called after the constructor of the class:


data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
 println("init : $name")
 }

 @Inject
 fun hello(){
 println("hello : $name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people:People
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_home)

 // Perform related injection operations 
 ...
 println(people.toString())
 }
}

The result of the operation is as follows:


01-02 11:57:30.995 16601-16601/? I/System.out: init : Tom
01-02 11:57:30.995 16601-16601/? I/System.out: hello : Tom
01-02 11:57:30.995 16601-16601/? I/System.out: People(name=Tom)

@Component

It can be understood as a syringe, which can be regarded as the core annotation in Dagger2, and is used to mark an interface or abstract class. An interface using the @ Component tag automatically generates an implementation class with an Dagger + class name to implement dependency injection at compile time. In Component, 1 can define two methods:

Members-injection methods:

This method has 1 parameter, indicating the class to be injected into, reminding Dagger to look for the attribute to be injected in the class (marked by @ Inject).


void inject(SomeType someType);// No return value 
SomeType injectAndReturn(SomeType someType);// Returns its parameter type 

Equivalent to:


MembersInjector<SomeType> getMembersInjector();// Use MembersInjector.injectMembers Method injection 

Provision methods:

This method has no parameters and returns 1 dependency to be injected (or provided). 1 is generally used when providing dependencies for other Component.


SomeType getSomeType();
Provider<SomeType> getSomeTypeProvider();// It can be passed through Provider.get Access any number of times 
Lazy<SomeType> getLazySomeType();// Pass Lazy.get No. 1 1 Create an instance on the second access, and access the same as in subsequent accesses 1 Instances 

class People @Inject constructor(val name:String = "Tom")
0

In fact, knowing that we can already use the simplest usage of Dagger2, after all, with dependency and syringe, we only need injection. Let's look at one of the simplest examples of Dagger2, which only uses @ Inject and @ Component to complete injection.

Step 1: Add annotation @ Inject to the constructor of the class to be injected


class People @Inject constructor() {
 fun hello(){
  println("hello")
 }
}

Step 2: Write a syringe interface


@Component
interface HomeComponent {
 fun inject(activity: HomeActivity)
}

Step 3: Inject


class People @Inject constructor(val name:String = "Tom")
3

Of course, this is only the simplest usage, and it is not applicable if you need to pass in 1 instance of a non-custom class. After all, you can't add @ Inject annotation to the third-party class. At this point, you need @ Module and @ Provides annotations.

@Module

Used to mark the class and provide dependencies for Component, it is equivalent to telling Component that you can come to me if you need dependencies, provided that Module is configured in Component. At the same time, Module can rely on other Module through includes.

@Provides

Used to mark a method in Module whose return type is the dependency type you need to provide.

For example in my own project, I need to create an pl2303 object in presenter, and the creation of pl2303 object requires context and pl2303Interface, so we need to provide three dependencies, because context is used elsewhere, which we propose separately:


class People @Inject constructor(val name:String = "Tom")
4

pl2303Interface only needs this one place:


class People @Inject constructor(val name:String = "Tom")
5

Among them, includes can be multiple, and we add ContextModule here, so there is only one pl2303Interface to create pl2303, which is an interface object, so new cannot be injected from the constructor. Next, create the syringe:


class People @Inject constructor(val name:String = "Tom")
6

Final injection:


class MainPresenter(val view: MainContract.View) : MainContract.Presenter, ActivityCallBridge.PL2303Interface, LifecycleObserver {
 @Inject lateinit var pl2303: Pl2303
 init {
  DaggerMainPresenterComponent.builder()
    .contextModule(ContextModule(view.context))
    .pl2303Module(Pl2303Module(this))
    .build()
    .inject(this)
 }
}

If there are many Module in an Component in a large project, Module without passing in parameters can be omitted. Look at the official comment document under 1:


public static void main(String[] args) {
  OtherComponent otherComponent = ...;
  MyComponent component = DaggerMyComponent.builder()
   // required because component dependencies must be set (Necessary) 
   .otherComponent(otherComponent)
   // required because FlagsModule has constructor parameters (Necessary) 
   .flagsModule(new FlagsModule(args))
   // may be elided because a no-args constructor is visible (Can be omitted) 
   .myApplicationModule(new MyApplicationModule())
   .build();
  }

@ Named and @ Qualifier

@ Named is an implementation of @ Qualifier. Sometimes we need to provide several dependencies of the same type (such as inheriting from the same parent class). If we don't do this, the compiler won't know which one we need and report an error, such as this:


class People @Inject constructor(val name:String = "Tom")
9

At this point, we need to mark 1 to tell the compiler which dependency we need:


@Module
class AnimalModule {
 @Provides
 @Named("dog")
 fun provideDog(): Animal = Dog()

 @Provides
 @Named("cat")
 fun provideCat(): Animal = Cat()
}
data class Pet @Inject constructor(@Named("dog") val pet: Animal)

As we said above, @ Named is only an implementation of @ Qualifier, so we can also use @ Qualifier to achieve the same effect. In actual use, it is recommended to use @ Qualifier, because @ Named needs handwritten strings for identification, which is prone to errors.

Attention should be paid to using @ Qualifier:

Creating a custom Qualifier requires at least two annotations, @ Qualifier and @ Retention (RUNTIME). Can have its own attributes.

We can look at the source code of @ Named for 1 time to deepen our understanding for 1 time:


@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
 /** The name. */
 String value() default "";
}

Let's transform the above example by comparing gourd painting with gourd painting:


@Module
class AnimalModule {
 @Provides
 @DogAnim
 fun provideDog(): Animal = Dog()
 @Provides
 @CatAnim
 fun provideCat(): Animal = Cat()
}

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DogAnim
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CatAnim
data class Pet @Inject constructor(@CatAnim val pet: Animal)

After testing, it can still run.


Pet(pet=cat)

@ Scope and @ Singleton

A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type

The @ Scope is used to mark a class that contains an injectable constructor or provides an injection dependent object. In short, it can be used to mark a class that contains the @ Inject constructor or the @ Module class.

@ Scope is used to manage the lifecycle of dependencies. It uses custom annotations like @ Qualifier1, while @ Singleton is the default implementation of @ Scope, similar to @ Named.

If a syringe and the place where dependent objects are created are not marked with @ Scope, a new object will be created every time it is injected. If @ Scope is marked, the same object will be used in the specified life cycle, especially if it is a singleton in the specified life cycle, not a global singleton, or it can be understood as a singleton in @ Component.

Or use the above example:


data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
  println("init : $name")
 }

 @Inject
 fun hello(){
  println("hello : $name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people: People
 @Inject
 lateinit var people_2: People
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_home)
  DaggerHomeComponent.builder()
    .build()
    .inject(this)
  println("people===people_2 : ${people===people_2}")
 }
}

Run results:


people===people_2 : false

Explain that they are really two different objects. Next, let's transform 1:


@Singleton
data class People constructor(val name: String) {
 ...// And before 1 Sample 
}

@Singleton
@Component(modules = arrayOf(AnimalModule::class))
interface HomeComponent {
 fun inject(activity: HomeActivity)
}
...//HomeActivity Code and previous 1 Sample 

Look at the running results again:


people===people_2 : true

Explain that both times are the same object visited. As mentioned above, this is only a local singleton, so how to realize a global singleton is very simple, as long as the marked Component is only initialized once globally, for example, it is initialized in Application, and the space-limited code will not be posted. Interested SAO Nian can practice it by himself.

Summarize


Related articles: