series

  • Dagger2 in Android (a) Popular basics
  • Dagger2 in Android (2) Advanced
  • Dagger2 in Android (iii) Scope with life cycle
  • Dagger2 in Android (四). Android extension library

Background knowledge

Dagger2 is an open source dependency injection framework maintained by Google (formerly Square). I tried to learn Dagger twice and eventually got so confused with all the nouns that I gave up without even writing a demo. Therefore, this article will also focus on explaining the various nouns of Dagger. Only when you are familiar with their functions can you use Dagger smoothly and understand others’ demo.

Despite the title Dagger2 in Android, the first few sections are all basic to the Dagger and have nothing to do with Android.

This series uses the Kotlin language, which is 100% Compatible with Java, except for syntax differences that don’t make a big difference. Kotlin will still end up being compiled into JVM bytecode.

Public String getName() {return “David”}

Kotlin version: fun getName(): String {return “David”}, even more succinct: fun getName() = “David”

Dependency injection

So first we need to understand what dependency injection is. Dependency injection is a solution to inversion of control (IoC). What is inversion of control? Don’t worry, I’ll try to explain all the concepts involved in the most general way possible.

Usually in object-oriented languages, one class tends to depend on other classes, and one object depends on other objects. So the most direct solution is to create a new one. It makes sense. I rely on computers, so I’m going to build my own. So the computer is, of course, controlled by me, which is called “control forward”. So the flip is, I need a computer, but I don’t build it myself, the manufacturer builds it and gives it to me. This is called inversion of control. Since I no longer control the production of the computer, the manufacturer is called the IoC container, which produces the objects to maintain, and I only use them. So IoC is not a technology, but an idea that can be used to reduce coupling between objects and increase code reuse. If the configuration of the computer changes, before everyone had to update their drawings, but now I just need the manufacturer to update and I can always get the latest model.

To achieve inversion of control, there are many solutions, common ones are dependency injection, dependency lookup, and service locator. Here we talk about dependency injection.

Dependency injection is nothing new, we use it every day. There are three common methods of dependency injection: constructors, setters, and annotations. Take the cook: the cook depends on the stove. The code could be written like this:

// Inject the stove through the constructor
class Chef(val stove: Stove) {
}

// Via Setter injection
class Chef() {
	var stove: Stove
	
	// Kotlin implements setters by default, I wrote one manually for clarity.
	fun setStove(stove: Stove) {
		this.stove = stove
	}
}
Copy the code

See, DI is all around us, we use it all the time. A cook needs a stove, but he doesn’t build it himself. Someone else makes it and passes it to him.

Dagger advantage

Why dagger when you can inject via a constructor? Of course because dagger is more convenient haha.

Continuing with the cook example, we know that stoves have to rely on fuel. So in order to get a cook, we have to get a fuel, then a stove, look at the following code:

class Stove(val fuel: Fuel) {}

class Fuel() {}// Create a cook
val fuel = Fuel()
val stove  = Stove(fuel)
val chef = Chef(stove)
Copy the code

See, in order to get a chef, we need to create a bunch of stuff. And if that’s not enough for you, chefs also rely on kitchen knives and gas for fuel. There will come a time when you are impatient.

In fact, sometimes two cooks can share one stove, and three stoves can share one bottle of fuel. All of these problems Dagger can be solved elegantly. How is it? Is it a little feeling <(~) ~)↗

Enter the theme

The introduction of Dagger

Dagger can be introduced in a number of ways, as shown in README. As Android we can use Gradle to declare dependencies (Kotlin) :

dependencies {
	def dagger_version = "2.23.1"
	implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
	
	// If you want to use Android's unique Dagger function, the following library is also introduced
	implementation "com.google.dagger:dagger-android:$dagger_version"
    implementation "com.google.dagger:dagger-android-support:$dagger_version"
    kapt "com.google.dagger:dagger-android-processor:$dagger_version"
}
Copy the code

@Inject

@Inject is the first Dagger annotation we touch. It serves two purposes: ① to mark what needs to be injected. ② Mark how these things are created.

So let’s say Dagger is the logistics department, then as a chef, you have to tell Dagger what you need, and then you have to tell him how to build it, so that Dagger can inject to you. So cook and stove (ignoring fuel for simplicity) we can rewrite it like this:

// Tell that the Dagger stove can be built with a no-parameter constructor
class Stove @Inject constructor() {}

class Chef() {
	@Inject // Tell Dagger I need a stove
	val stove: Stove
}
Copy the code

It’s that simple. Now we have a Dagger that tells us what we need and how the object is created. I’m going to call the cook the goal category, which is, “Where is the stove going?” That’s the goal.

@Component

We’ve had this invisible connection between the cook and the stove. But to really get the stove, you have to make the connection a little bit more direct. After all, it was all about generalities, we had to do it in a specific chef. The bridge is @Component. Component will specifically connect the stove on which the cook depends to the stove constructor.

Component is an annotation class and is an interface or abstract class. So you have to tag an interface @Component to get a Component. It gets a reference to an instance of the target class (that is, a live cook) and looks up the classes that the class depends on (ask the cook what you need). Once it gets the answer, it looks for a known constructor (previously annotated at Inject), instantiates it and injects it into the target class (make a stove and give it to the cook).

With the above explanation, we could easily write a Component:

@Component
interface MainComponent {
	// Define a function to get a reference to the target class instance
	fun inject(chef: Chef)
}
Copy the code

So far we have implemented a full dependency injection: tell the Dagger chef the stove is needed, tell the stove how to build it, and build a direct bridge.

A: congratulations! (. ⌒ ∇ ⌒)

@Module

What the hell is that? Let’s think about what happens if a cook can’t build a stove. As reflected in the project, we introduced a third party library. This library does not mark @inject on the constructor. We cannot modify the source code to add it ourselves. At this time, Module comes out. The Module wraps a third party library around a creation method that informs the Dagger. For example, if the cook can’t build a stove, but he knows where to buy one, then this is OK.

In this vein, let’s modify the code of the cook stove:

// Now remove the @inject of stove to mean chef can't build stove
// If stove is a third-party library, we have no right to add @Inject
class Stove() {}@Module
class MainModule() {
	// I can't make it, but I can buy it at [MainModule]
	provideStove():Stove {
		return Stove()
	}
}
Copy the code

The Module is like the factory pattern, which provides various methods for creating instances.

Now we must let the @Component know that the Module (store) exists. Very simple:

// Just pass in the Mudoule class array
@Component(modules = [MainModule::class])
interface MainComponent {
	fun inject(chef: Chef)
}
Copy the code

Then there is the new problem that the @Inject annotation of class constructors is now annotated as a store without specifying how a particular class (stove) is actually obtained. It is as if the logistics office now knows that it is possible to buy a stove in Carrefour, but does not know in which area.

A Module can be referenced by multiple Components. Because it’s possible that many supermarkets sell stoves.

@Provides

Provides will finally solve the problem of third-party libraries. We use the Provides annotation for all functions in the Module that create instances. So these functions are going to be identified by the Dagger and then select a return value type that matches.

For the third party stove the chef relies on now, Dagger will handle this: First the bridge tells him that there is a store (MainModule) that may have a stove, then goes to the store and filters all the shelves to find a matching item and bring it back.

@Module
class MainModule() {
	@Provides // Add a comment indicating that this function can provide a stove (or other item)
	provideStove():Stove {
		return Stove()
	}
}
Copy the code

So far, we’ve seen the Dagger basics. We have learned how to notify the required dependencies for Dagger, how the dependent classes are created, and how to wrap and enable the Dagger recognition for classes provided by third parties.

Hopefully the chef analogy will help you understand the concepts better, and we’ll move on to other notes below.