Dagger Series: Something amazing about constructor injection

dagger-in-android.png?w=2924

In Dagger, it provides another way to inject/provide resources by using constructor injection. The typical case is that we use constructor injection in a class to inject needed resources as class parameters. In this way, we can directly use the required resources without declaring the @Inject keyword. Another pro tip is that it has very high potentialities to let your injection more robust and more flexible. Let’s see how we can use constructor injection for providing multi-source injection for the different release versions.

First of all, please check my repo. Let’s see the data models in the project at the beginning; I’ve done many changes for these data models.

Data Model Changes

For a car resource, it’s probably a petrol engine or an electric engine. We can create an engine interface; it’s just a simple interface with a method - getEngineName() to let the driver know which engine the car uses.

1
2
3
interface Engine {
fun getEngineName(): String
}

As I mentioned before, we could have several different engines for this car.

PetrolEngine

1
2
3
4
class PetrolEngine @Inject constructor(): Engine {
private val name: String = "PetrolEngine"
override fun getEngineName(): String = name
}

ElectricEngine

1
2
3
4
class ElectricEngine @Inject constructor(): Engine {
private val name: String = "ElectricEngine"
override fun getEngineName(): String = name
}

Both of these engines could have their properties, and I use the constructor injector for these two classes, which means these two car engines can provide themselves as resources. The constructor injection is much easier than creating a module to provide the resource. A class that uses this way would be automatically injected into a component.

In this case, if we directly use these two classes, it would work. But we need to assign one of these engines in our project explicitly. It has a more flexible way to do. Let’s see.

ElectricEngineModule

1
2
3
4
5
@Module
abstract class ElectricEngineModule {
@Binds
abstract fun engine(engine: ElectricEngine): Engine
}

PetrolEngineModule

1
2
3
4
5
@Module
abstract class PetrolEngineModule {
@Binds
abstract fun engine(engine: PetrolEngine): Engine
}

I create two modules for these two different engines, respectively. But I don’t use @Provides, instead, I use @Binds. If you don’t understand what’s difference between @Provides & @Binds, I would suggest you should know these two keywords first. The differences are not only on performance but also for a dynamic linking here.

Both modules have a function called engine and return an Engine type. That shows a possibility: we can get two different engines via a feature flag. Let’s see how to do that.

ElectricEngineComponent

1
2
3
4
5
6
@Component(
modules = [
ElectricEngineModule::class
]
)
interface ElectricEngineComponent: EngineComponent

PetrolEngineComponent

1
2
3
4
5
6
@Component(
modules = [
PetrolEngineModule::class
]
)
interface PetrolEngineComponent: EngineComponent

I create two components for these two modules separately. You might notice those two components all extended from the EngineComponent. Then what’s the EngineComponent?

EngineComponent

1
2
3
interface EngineComponent {
fun engine(): Engine
}

It’s just a primary interface with a function engine(). What’s this purpose for this interface? Don’t worry, and we would use it into our ApplicationComponent as a dependency. Let’s take a look.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component(
dependencies = [
EngineComponent::class
],
modules = [
...
]
)
interface ApplicationComponent {

@Component.Builder
interface Builder {
@BindsInstance

fun engine(engineComponent: EngineComponent): Builder
...
}
}

You might wonder: can we treat this EngineComponent as a dependency in a real Dagger component? Yes, Dagger allows us to do this. And you can see, I also create a builder for this EngineComponent. Ok, even though we can do this, what does it mean? Let’s go back to see the Mercedes class in the project. This class implements the Car interface and accepts an injection - Engine. That means for each different Mercedes Benz car, and it might either use a petrol engine or an electric engine that depends on what model it is.

Mercedes

1
2
3
4
5
6
7
8
9
10
class Mercedes @Inject constructor(
val engine: Engine
): Car{

private val carModel: String = "mercedes-benz GLC 300"

override fun getModelName(): String = carModel
override fun getModelDetail(): String = carModelDetail
override fun getEngineType(): String = engine.getEngineName()
}

The magic part is: we don’t need to give this Mercedes Benz car a specific engine setting. Instead, we can ignore that and wait for an engine injection.

Then, how to inject an engine into this car?

AppInjector

1
2
3
4
val applicationComponent = DaggerApplicationComponent.builder()
.application(application)
.engine(DaggerPetrolEngineComponent.create())
.build()

I injected a petrol engine into a component that means this car should have a petrol engine. Let’s see.

dagger-constructor-injection-e1566702688746.png
Dagger Petrol Engine

What about an electric engine?

AppInjector

1
2
3
4
val applicationComponent = DaggerApplicationComponent.builder()
.application(application)
.engine(DaggerPetrolEngineComponent.create())
.build()

dagger-constructor-injection-2-e1566702816979.png
Dagger Electric Engine

What about a particular version engine?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# in AppInjector
val applicationComponent = DaggerApplicationComponent.builder()
.application(application)
.engine(object : EngineComponent {
override fun engine(): Engine {
return specialVersionEngine()
}
})
.build()

# specialVersionEngine
class specialVersionEngine: Engine {
val name: String = "SpecialVersionEngine"
override fun getEngineName(): String = name
}

dagger-constructor-injection-3-e1566703073365.png
Dagger Special Version Engine

You can see how flexible it is. In this way, we basically can create whatever engine we want and provide this engine to a car in a natural way.

Happy coding, enjoy.