In this article we will discuss Dagger 2. To use dagger 2 in our project, first of all we need to add dagger 2 dependencies to the app level build.gradle file of our project. We can find them from this google dagger official github page. Copy these two dependencies. Paste them into build.gradle.
// Add Dagger dependencies
dependencies {
implementation 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
As we discussed during previous article, objects used by one object are called dependencies of that object. An example this smart phone object has three direct dependencies. Battery, Memory Card and SIM Card. SIM Card has on dependency, the Service Provider. So, the Service Provider becomes and indirect dependency to the Smart Phone.
class SmartPhone(val battery: Battery, val simCard: SIMCard, val memoryCard: MemoryCard) {
init {
battery.getPower()
simCard.getConnection()
memoryCard.getSpaceAvailablity()
Log.i("MYTAG", "SmartPhone Constructed")
}
fun makeACallWithRecording() {
Log.i("MYTAG", "Calling.....")
}
}
class SIMCard() {
public lateinit var serviceProvider: ServiceProvider
init {
Log.i("MYTAG","SIM Card Constructed")
}
fun getConnection(){
serviceProvider.getServiceProvider()
}
}
Here in the main activity we construct these instances. [Dagger 2]
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val battery=Battery()
val memoryCard=MemoryCard()
val serviceProvider=ServiceProvider()
val simCard=SIMCard(serviceProvider)
val smartPhone = SmartPhone(battery, simCard, memoryCard)
smartPhone.makeACallWithRecording()
}
}
That’s what we want to avoid with the support of dagger 2. In order to have a loosely coupled, maintainable, testable code we need to get them to the main activity form an outside source instead of constructing them here.
Let’s add a instance variable of the Smart Phone . Now we want dagger to construct a smart phone and send it to this main activity. As we discussed during a previous Article,
class MainActivity : AppCompatActivity() {
private lateinit var smartPhone: SmartPhone
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerSmartPhoneComponent.create()
.getSmartPhone()
.makeACallWithRecording()
}
}
First of all dagger has to construct a service provider instance, and then using that dagger can construct a SIM Card instance. Dagger has to also construct a Battery instance and a Memory Card instance.
After that, using Battery, Memory Card and SIM Card instances dagger can construct a Smart Phone instance. Dagger do not create objects for all the classes in the project.
If we have given permission only dagger would do so…
We can use @Inject annotation to tell dagger, or to give permission to dagger to use a constructor to make an object of that class.
When it is required to inject the object as a dependency. This injection is somewhat recursive, if an inject annotated constructor has other dependencies as parameters, dagger will look for the ways to inject them as well.
Further, start form the constructor of the Smart Phone class. In Kotlin primary constructor is invisible. But we need a visible constructor to annotate it with inject annotation. We need to use the explicit constructor keyword to make this visible.
Then add the inject annotation. This constructor has 3 parameters. So we need to inject their constructors as well. Otherwise dagger will not be able to create objects of those dependencies.
class SmartPhone @Inject constructor(val battery: Battery, val simCard: SIMCard, val memoryCard: MemoryCard) {
init {
battery.getPower()
simCard.getConnection()
memoryCard.getSpaceAvailablity()
Log.i("MYTAG", "SmartPhone Constructed")
}
fun makeACallWithRecording() {
Log.i("MYTAG", "Calling.....")
}
}
Furthermore, Let’s go to battery class. Make the constructor visible. And annotate it will Inject. Now, let’s switch to SIM Card class. Again , make the constructor visible. and annotate it will Inject. This SIM Card has a Service Provider as a parameter .
Moreover.. [Dagger 2]
Let’s inject the constructor of the Service provider class as well. Then, the Memory card class. Next, make the constructor visible. And annotate it with inject annotation. What we just did here is called, constructor injection.
When you are using dagger 2 for dependency injection, it is highly recommended to use constructor injection for every possible scenario. Now considering the instructions provided by us, dagger can generate codes to construct dependencies represented by this dependency graph.
To use dagger 2 generated codes for dependency injection we also need an interface annotated with @Componentannotation.
Let’s create a new interface. Name it as Smart Phone Component. Annotate the interface with @Component annotation. Now we can write an abstract function to get the dependency we want. The name of the method is not important. We can write any function name we like.
But the return type of the function should be the type of the dependency you want. Let’s name the function as get Smart Phone. Return type should be Smart Phone. When generating codes for dependency injection dagger will only consider this return type.
@Component
interface SmartPhoneComponent {
fun getSmartPhone() : SmartPhone
}
Now, let’s go back to main activity..
First of all we need to rebuild the project. Because dagger has not generated codes yet. Second, by rebuilding the project we can get them generated by dagger . Now if you check this generated Java package, you will see newly generated codes by dagger considering the instructions provided by us using annotations.
Moreover, You can see dagger has created factory classes for each dependency. And for the component interface dagger has created a class named Dagger Smart Phone Component implementing the interface. When generating a class which implements component interface, dagger always include the word Dagger in front of the interface name.
Further, To get a Smart Phone dependency we defended a getter function. And dagger has implemented that here. So we can use it in the Main Activity. Dagger Smart Phone Component. This is the newly generated class. Create(). Then getSmartPhone() this will inject a smart phone instance. Then invoke smartphones.makeACallWithRecording() Function.
class MainActivity : AppCompatActivity() {
private lateinit var smartPhone: SmartPhone
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerSmartPhoneComponent.create()
.getSmartPhone()
.makeACallWithRecording()
}
}
Working with module in dagger 2
When we are going to use dagger We should try to implement constructor injection for all the classes we own. That means , we should go to the class and ,type inject annotation in front of the constructor like we did in the previous article.
That is the easiest and most efficient way. But in many practical scenarios, we cannot modify the class. We are not allowed to open the class and add inject annotation to the constructor,
As an example, when we are using a Retrofit client , Since Retrofit is a third party library. We build it using its builder function. When we are using the classes we don’t own, classes form third party libraries, we cannot open the class and add @Inject annotation to the constructor.
Also , there are other cases like Context object, in which case we cannot instantiate the dependency. So, for that type of situations, We have to use modules and write provider functions to provide those dependencies. Now, Just for the demonstration of the concept, let’s assume that we don’t own this Memory Card class.
Therefore , since we don’t own it we should not be able to add this @Inject annotation here.
For now…
Just think about this memory card class, as it comes from a third party library. Then create a module class for this dependency. To make this a dagger module we need to annotate it with module annotation. Now create a provider function with the Return type of Memory Card.
Name it as provides Memory Card. You can give any name for the function. But we usually start the name with provides. Then, We need to construct a Memory Card instance and return it. And We need to annotate this provider function with @provides annotation. We should always annotate provider functions with @provides annotation. Otherwise dagger will not recognize it as a provider function.
Marking a function with this annotation tells dagger, that this function provides an object of the return data type. Now open the Smart Phone Component interface. and link our module to this component. This is how we do it. Then, to generate dagger related classes, let’s rebuild the project.
State of module
When we are using dagger, It is discouraged for modules to have a state. But for some scenarios it is a requirement. We might need to define an instance variable inside a module and pass a value for it. To demonstrate this I am including an int variable called memorySize to this MemoryCard Module.
@Module
class MemoryCardModule(val memorySize:Int) {
@Provides
fun providesMemoryCard():MemoryCard{
Log.i("MYTAG","Size of the memory is $memorySize")
return MemoryCard()
}
}
First this memory card module has a state. second, go to the MainActivity.kt . Since at least one of our modules has a state, we cannot use this simple create function,
DaggerSmartPhoneComponent.create()
.inject(this)
smartPhone.makeACallWithRecording()
to construct a SmartPhoneComponent. Instead we have to use the builder. DaggerSmartPhoneComponent.builder() When we are constructing the component with a builder function , we have to add each module with a state here.
DaggerSmartPhoneComponent.builder()
.memoryCardModule(MemoryCardModule(1000))
.build()
.inject(this)
}
memory Card Module notice we have used simple m here. Then we need to pass an instance of the same module. Let’s insert 1000 for the memory size. Then Build. Finally inject.
Hope this article suffices what you need to learn about. Check out my blog for more such posts.
Thanks!
Codinglance