Table of contents
In our last post, we tried to create a bare-bones app in Kotlin to understand what MVVM is and how it could be helpful in building large-scale projects. But modern projects don't only rely on MVVM but use a mix of MVVM and Dependency Injection principles and other third-party libraries like Retrofit, RoomDB, etc. to achieve a very high level of decoupling across classes and easy testability.
While all of these words might sound like technical jargon to you right now, we'll slowly try to create one of our own projects with both MVVM and Dependency Injection implemented, to better understand the concept.
If you don't already have much idea about dependency injection, try skimming through this series to get a high-level understanding of the subject.
The Project
We'll be trying to create a simple signup and login flow where we'll be able to create new users i.e. sign up and then validate their credentials using the login APIs.
Code to this blog post can be found here
Setting up the project
Let's start with a fresh project and begin by adding Dagger2 dependencies to it.
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt' //do not forget to add this
}
android {
buildFeatures {
dataBinding true //will be used later for data binding
}
//... Rest of the code as it is.
}
dependencies {
def dagger_version = '2.46.1'
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
//dagger android
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"
//other dependencies...
}
Let's go ahead and sync up the project.
Step 1
If you're completely new to Dagger2 and would like to understand the steps being followed here in much detail, try reading the Dependency Injection Series
Let's go ahead and create a new package named di
into our main folder and add the entry point for our Dagger graph i.e. the AppComponent
interface to it. Remember, that this entry point is what Dagger uses to decide the order in which dependencies should be created.
@Component(modules = [AndroidSupportInjectionModule::class])
interface AppComponent : AndroidInjector<MyApp> {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application): AppComponent
}
}
Understanding the code
If you remember from our blog post on Injecting into Android Framework, the
AndroidSupportInjectionModule
helps us easily inject Android framework classes into our Dagger Graph without having to write boilerplate code to create subcomponents for Activities, Services, etc.@Component.Factory
: This is another Dagger 2 annotation used to denote the factory interface that will be used to create instances of theAppComponent
.interface Factory
: This defines a nested interface namedFactory
inside theAppComponent
.fun create(@BindsInstance application: Application): AppComponent
: This function is defined inside theFactory
interface. It takes anApplication
instance as a parameter and returns anAppComponent
instance. The@BindsInstance
annotation is used here to tell Dagger to use the providedApplication
instance whenever a dependency of typeApplication
is needed. This way, you can pass yourApplication
instance to the Dagger graph and use it to satisfy dependencies.
More on Component Builders & Factories in these two blog posts.
Step 2
Now, let's go ahead and update our MyApp class as follows.
class MyApp : DaggerApplication(), HasAndroidInjector {
@Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this)
}
override fun onCreate() {
super.onCreate()
}
override fun androidInjector(): AndroidInjector<Any> = androidInjector
}
Note that without using DispatchingAndroidInjector
, you will need to manually inject your Android components (like Activities, Fragments, etc.) by getting hold of the AppComponent
instance and calling the appropriate inject methods on it. This means you would need to define inject methods in your AppComponent
for each component you want to inject. This can increase the amount of boilerplate code in your application compared to a setup that uses DispatchingAndroidInjector
.
override fun applicationInjector(): AndroidInjector<out DaggerApplication>
: This method overrides a method from theDaggerApplication
class. It is used to provide theAndroidInjector
which will be used to inject dependencies into theDaggerApplication
.return DaggerAppComponent.factory().create(this)
: Inside theapplicationInjector
method, an instance ofAppComponent
is created using its factory method, and the currentApplication
instance (this
) is passed to it. This sets up the Dagger 2 dependency injection graph with theApplication
instance bound to it.
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun androidInjector(): AndroidInjector<Any> = androidInjector
: This method provides theandroidInjector
that will be used to inject dependencies into Android framework types. It returns theDispatchingAndroidInjector
instance that was injected into theandroidInjector
field earlier.
Conclusion
And that's it for this now, we'll slowly build on top of this component by adding more modules and sub-components to our project as we move ahead and create our dagger graph for the project. Till then, happy coding!