Building a basic App with MVVM & DI in Android: (Day 03)

Photo by Louis Tsai on Unsplash

Building a basic App with MVVM & DI in Android: (Day 03)

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

  1. 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.

  2. @Component.Factory: This is another Dagger 2 annotation used to denote the factory interface that will be used to create instances of the AppComponent.

  3. interface Factory: This defines a nested interface named Factory inside the AppComponent.

  4. fun create(@BindsInstance application: Application): AppComponent: This function is defined inside the Factory interface. It takes an Application instance as a parameter and returns an AppComponent instance. The @BindsInstance annotation is used here to tell Dagger to use the provided Application instance whenever a dependency of type Application is needed. This way, you can pass your Application instance to the Dagger graph and use it to satisfy dependencies.

More on Component Builders & Factories in these two blog posts.

  1. Component Builders & Factory: (Day 12)

  2. Custom Scopes & Subcomponents in DI: (Day 11)

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.

  1. override fun applicationInjector(): AndroidInjector<out DaggerApplication>: This method overrides a method from the DaggerApplication class. It is used to provide the AndroidInjector which will be used to inject dependencies into the DaggerApplication.

  2. return DaggerAppComponent.factory().create(this): Inside the applicationInjector method, an instance of AppComponent is created using its factory method, and the current Application instance (this) is passed to it. This sets up the Dagger 2 dependency injection graph with the Application instance bound to it.

override fun androidInjector(): AndroidInjector<Any> = androidInjector
  1. override fun androidInjector(): AndroidInjector<Any> = androidInjector: This method provides the androidInjector that will be used to inject dependencies into Android framework types. It returns the DispatchingAndroidInjector instance that was injected into the androidInjector 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!