How To Use Dependency Injection For Your MVVM Android App

Erman Derici
Huawei Developers
Published in
6 min readNov 30, 2023
Confused Droid

Introduction

Hello,

In this article we will go over what dependency injection is, why you should use it and how you should use it.

What Is Dependency Injection?

Dependency injection is a widespread technique for a clean app architecture. The benefits of good dependency injection implementation are:

  • Easier refactoring
  • Easier testing
  • Making your code reusable

That’s OK so far, but what exactly is dependency injection? Let’s explain it with an example.

Say, you have a Car. Cars are made up of different parts, such as wheels, motor, the chassis and such. Cars and their parts are manufactured in factories, but different parts are usually made in different factories. A car engine factory is not responsible for also manufacturing a car tire.

Tire manufacturing process example

Following this logic, take a Car class in your code for example. For the sake of keeping this short, we will assume your Car class has only an engine. Normally you’d write it like:

Car class without Dependency Injection

This implementation has a few issues, namely that the Car class and the Engine class are tightly coupled. If you wanted to use different subclasses or implementations, you’d have problems on your hands.

Ok, this is bad. How can we use dependency injection to remedy the issues we have mentioned? Well, there are 2 ways you can use dependency injection:

  • Constructor Injection
  • Field / Setter Injection

Constructor injection is the more commonly used one, but there are instances where you’d need to use setter injection.

Here is how you’d use the constructor injection:

Car class with constructor Dependency Injection

We have created our Engine class outside our Car class, and instead we have provided the Engine as a parameter.

For setter injection, the approach is as follows:

Car class with setter Dependency Injection

We have created our Car class with a lateinit Engine, which we have set to be a new engine after our Car class was created in our main function.

Dependency injection technique allows you to reuse your Car class with different Engine implementations or subclasses. Basically, and overly simplified, instead of creating different instances inside your class you just pass an instance as your parameter.

With the code examples, I hope you have understood what dependency injection is at its core. Because we will be moving on to automated dependency injections in our MVVM app 😊

Smug Dog

Automated Dependency Injection

These days, there are libraries and dependencies for almost anything. Dependency Injection is no exception. For Android / Kotlin, I will be focusing on Hilt.

What Is Hilt?

Hilt is a dependency injection library built on top of Dagger that reduces the boilerplate code by automatically handling dependency injections while being life cycle aware.

Implement Hilt in your app by adding the following line in your root level build.gradle file:

plugins {
...
id 'com.google.dagger.hilt.android' version '2.44' apply false
}

Then apply the plugin in your app level build.gradle file:

...
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}

android {
...
}

dependencies {
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
}

// Allow references to generated code
kapt {
correctErrorTypes true
}

Hilt uses Java 8 features, so be sure to enable Java 8 by adding the following to your app level build.gradle file:

android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

Using Hilt

Hilt requires all applications to have an Application class annotated with @HiltAndroidApp tag:

@HiltAndroidApp
class ExampleApplication : Application() { ... }

Now that you have your Application class, we can move on to other classes where you will be injecting your dependencies to. Hilt currently supports these Android classes:

  • Application (@HiltAndroidApp)
  • ViewModel(@HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

If you annotate any class with @AndroidEntryPoint then you are required to annotate other classes that depend on it. For example, you can not inject an annotated fragment to an activity that doesn’t have an annotation.

Generally, you can use @Inject annotation to inject your class to your entry point as a field:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

@Inject lateinit var injectionTest: InjectionTestClass
...
}

But sometimes, you will not be able to use the constructor injection method:

class InjectionTestClass @Inject constructor(
private val service: InjectionTestService
) { ... }

That is because Hilt needs to know how to generate the InjectionTestService class, and Interfaces along with classes that you do not own (third party classes) can not be instantiated this way. For this reason, we will be using Hilt Modules.

Hilt Modules

Hilt Modules contain instructions for Hilt to help instantiate classes. These are separate classes that you will need to manually write for all cases we have seen before.

There are 2 distinct ways to do this. One involes the @Provides annotation while the other involves the @Binds annotation.

Here is the @Provides example:

@Module
@InstallIn(ActivityComponent::class)
object InjectionTestModule{

@Provides
fun provideInjectionService(): InjectionTestService{
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(InjectionTestService::class.java)
}
}

and here is the @Binds example

@Module
@InstallIn(ActivityComponent::class)
abstract class InjectionTestModule{

@Binds
abstract fun bindInjectionService(
InjectionTestServiceImpl: InjectionTestServiceImpl
): InjectionTestService
}

The main difference can be simplified to @Binds is used to bind an interface to an implementation while @Provides can be used to provide instances of third party classes.

This pretty much concludes how you can use Hilt for dependency injection. There are some functionalities I have skipped for the sake of simplicity and keeping this article short. You can find more detailed information in the references section down below.

Using Hilt In An App / Putting It All Together

Now that we know how to use Hilt, we can take a look at an example MVVM app.

Our structure will consist of:

  • Application Class
  • Activity or Fragment Class
  • ViewModel Class
  • Use Case Class
  • Repository / Implementation Classes

Start by creating your application class:

Application Class

Then let’s create our repository class where we will do the data retrieval operations. Let’s not forget to create the respective modules for Hilt.

Create your Interface first:

Repository Interface

Then create your implementation:

Repository Implementation

Then create your module so that Hilt can create your instance. You can also use the @Binds annotation here:

Repository Module

Finally, since we have a Firestore dependency in our repository, we will need to write a @Provides annotated method for it as well:

Firebase Module

Next step is to create your use cases. The use cases will invoke the repository methods:

Use Case
Use Case Module

Following your use cases, you can create your ViewModel now. We will invoke the use cases in our ViewModel classes:

View Model

Conclusion

In this article, we have seen what dependency injection is, what types of dependency injection exists, how to use Hilt and an example for MVVM architecture with Hilt for dependency injection.

This way, you are easily able to refactor your application. For example, if you decided to migrate to Huawei Cloud Database for example, you’d only need to change your repository class and maybe your Model class. The refactoring is easy and simple.

This has been quite a lengthy article, if you are still here I’d like to thank you. I really appreciate your support.

Cat Wiggle

References

--

--