In this article we will be working with Dagger 2 in Android. Its Part II of this topic. .In some cases we may have an Interface as a dependency instead of a class. To show you how to work with interfaces, Let’s change this battery class to an interface. Interfaces do not have a constructor. So let’s remove this constructor And we need to make this function abstract.
Furthermore here in this Smart Phone’s constructor we have Battery as a parameter. But now Battery is an interface. it will not work. Because at this point there is no way to construct a battery. To work with this We need to create a class which implements the battery interface and provide the dependency through a module.
So let’s create a new Kotlin class..
Moreover, This should implement the battery interface. Then implement the get Power function. We should annotate the constructor of this class with @Inject annotation. During this, Android studio will show an error message. Here we have a dependency of Nickel Cadmium Battery. As it has implemented Battery interface we know this is a Battery.
But dagger does not know that yet. Dagger does not type cast for dependencies in that way. We have to create a module and provide this Nickel Cadmium Battery dependency as a Battery dependency.
So, let’s create a new Kotlin class for the module...
@Module
class NCBatteryModule {
@Provides
fun procideNCBattery(nickelCadmiumBattery: NickelCadmiumBattery):Battery
{
return nickelCadmiumBattery
}
}
this function will return a Nickel Cadmium Battery instance type casted as a battery instance. Dagger recognizes this. returned dependency by considering the return type of the provider function. Next, dagger will recognize this dependency as a battery. Now let’s go back to the Smart Phone Component interface.
@Component(modules = [MemoryCardModule::class,NCBatteryModule::class])
interface SmartPhoneComponent {
fun getSmartPhone() : SmartPhone
}
Here we need to add, our newly created module to the modules list. So, that’s how we use an interface type as a dependency. let’s run the app to see how this works. we can make this code even more concise.
In our app..
we have already annotated the constructor of the Nickel Cadmium Battery class with inject annotation. Therefore we don’t need a body for this provides function. So we can just define this module as an abstract module. then make this function as abstract. And remove the function body.
In dagger abstract functions cannot be annotated with @Provides annotation. But dagger has provided an option to annotate them with @Binds annotation. Let’s also change the function name.
@Module
abstract class NCBatteryModule {
@Binds
abstract fun bindsNCBattery(nickelCadmiumBattery: NickelCadmiumBattery):Battery
}
Field injection with dagger 2
To get the Smart Phone dependency, we have invoked this get Smart Phone function of the smart Phone Component interface form the Main Activity, But this is not the best and most efficient way of doing it. There can be many activities and fragments in a project. And there can be many dependencies.
Think…
If you have 10 required dependencies like this Smart Phone dependency, you may have to write getter methods for all of them in the component interface. And you will have to call to them form all activities in this way. This will become more difficult if you have to pass values to those dependencies at runtime.
Dagger library doesn’t expect us to write getter methods for dependencies like this. We can just get them injected to the activities or fragments easily using field injection. Let me show you how to do it, go to Smart Phone Component. We don’t have to write getter methods like this. Actually, we can get injected all dependencies belong to the dependency graph.
Next… [Dagger 2]
Now, I am creating an abstract inject function for the MainActivity. fun inject Parameter is MainActivity. Here,I have used inject as the function name. You can use any name you like. But as a convention we usually use inject as the name of the injector function.
We are going use this function for the main activity. So I added an instance of main activity as a parameter. If in case you need to use this component in 3 activities and one fragment, you may need to write 3 more inject functions here with those activities and fragment as parameters.
@Component(modules = [MemoryCardModule::class,NCBatteryModule::class])
interface SmartPhoneComponent {
fun inject(mainActivity: MainActivity)
}
Let’s head over to Main Activity [Dagger 2]
We can invoke the inject function here, passing the activity as a parameter.
let’s annotate this smart phone instance variable with @Inject annotation. Then, we can invoke the makeACallWithRecording() function of the smartphone.
class MainActivity : AppCompatActivity() {
@Inject
lateinit var smartPhone: SmartPhone
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerSmartPhoneComponent.create()
.inject(this)
smartPhone.makeACallWithRecording()
}
}
But what is the difference. Why we did this new change. If we invoke a function like getSmartPhone. We will be able to only get that SmartPhone dependency. If we wanted get another dependency we would have to create another function in the component interface and invoked it here.
But , now we don’t need to create a specific function for each and every dependency .Instead we can get those dependencies to the Main Activity using field injection.
Example…
Let’s assume we need to get a memory cared instance as a dependency.
All we need to do this. Just declare an instance variable and annotate it with @Inject annotation. dagger will provide the memory card dependency. This approach is very easy and very efficient. With the support of dagger we can get all the dependencies we want using field injection.
Application class [Dagger 2]
Application class is the base class of an Android app . All the content of this class is predefined by the Android Framework. Even though we cannot change the application class , we can give additional instructions to it by extending it. Application class and its sub classes will be instantiated before all the activities, fragments or any other application objects of the Android app.
If there are tasks that need to run before the creation of first activity we should create a Custom Application Class. If there are immutable data such as a shared network client object Or If there are global objects such as data persistence and crash reporting, that needs to be shared across all components, we should create a Custom Application Class. In our demo app we created the component in our main activity.
Next….
If there were 5 activities we would have to write this same code five times. The recommended best practice here is writing this code part in a subclass of the application class.
So, let’s create a subclass of application class now. I am naming it as SmartPhoneApplication Superclass of this class is the Application class.
class SmartPhoneApplication : Application() {
lateinit var smartPhoneComponent: SmartPhoneComponent
override fun onCreate() {
smartPhoneComponent = initDagger()
super.onCreate()
}
private fun initDagger(): SmartPhoneComponent =
DaggerSmartPhoneComponent.builder()
.memoryCardModule(MemoryCardModule(1000))
.build()
}
Now, Let’s switch back to main activity.
class MainActivity : AppCompatActivity() {
@Inject
lateinit var smartPhone: SmartPhone
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as SmartPhoneApplication).smartPhoneComponent
.inject(this)
}
}
Here, We will write codes to get the application, And then get the smart Phone Component instance from it and finally invoke its inject function passing this activity. application as Smart Phone Application then, smart Phone Component inject, this. There is one special thing to do. We need to add the name of the application class we created to the Manifest file. So, let’s open the AndridManifest.xml file.
android:name=".SmartPhoneApplication"
Singleton [Dagger 2]
Every time we invoke this inject function of the Smart Phone Component interface Dagger constructs a new smart phone object and inject it to the Activity. Every time we invoke the inject function dagger constructs a new Smart Phone object in the memory. Not only that, in order to do so, dagger has to always construct Service Provider, SIM Card ,Memory Card, and Nickel Cadmium battery objects as well. This is very inefficient. In some use cases , constructing multiple instances of the same class might cause unexpected behaviors. We can easily avoid this using @Singleton annotation.
Now let’s open the smartphone class and annotate it with Singleton annotation.
@Singleton
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.....")
}
}
Now, Since we have annotated this class with the singleton annotation, We have to also annotate its component interface with the singleton annotation. Let’s open the Smart Phone Component interface. And annotate it with Singleton.
@Component(modules = [MemoryCardModule::class,NCBatteryModule::class])
interface SmartPhoneComponent {
fun inject(mainActivity: MainActivity)
}
Hope this article suffices what you need to know.
Thank you!
Codinglace