Dagger 2 for Android, Part I ー What is Dependency Injection?

Tan Jun Rong avatar

Tan Jun Rong

Photo by Federica Galli on Unsplash
Photo by Federica Galli on Unsplash

If you're doing Android development, you might have came across the Dagger 2 library, which is used for Dependency Injection. Many Android developers find it useful, but this is a library with a steep learning curve similar to RxJava. I myself struggle to understand Dagger 2.

In my journey of studying Dagger 2, I want to write down what I've learned.

Before jumping into Dagger 2, we need to understand what is Dependency Injection. So this article will explain about this concept. 📚

Note: Examples will be given in Kotlin language with the context of Android development


This is part of my Dagger 2 blog series:


Concept

There are a few types of depedency injection. In this article, we will talk about:

  • Constructor Injection
  • Method Injection

Each methods has it's own pros and cons. But what important is to understand what Dependency Injection itself is, and the trade-off between them. Let's take a look at the first method Constructor Injection to begin with. 💪


Constructor Injection

Say we have a Person name John

class Person {
    val name: String = "John"
}

In the caller side, we can print the name like this 👇

fun main(args: Array<String>) {
    val john = Person()
    println("the name is ${john.name}")
}

Now imagine that we need to have a Kelvin, but we cannot change the name to Kelvin. Because whenever we make an instance of Person. The name will automatically become John.

To solve this, we can apply dependency injection concept.

Person depends on name, so we can say name is a dependency of Person. "Dependency Injection" simply means injecting this dependency into Person.

This can be achieved by making name a constructor parameter.

class Person(val name: String)

Now in main.kt, we can simply inject the dependency, name into it.

Here's a re-write of the code for John:

fun main(args: Array<String>) {
    val john = Person("John")
    println("the name is ${john.name}")
}

If you need a Kelvin person, sure!

Just pass the name "Kelvin" into the constructor:

fun main(args: Array<String>) {
    val kelvin = Person("Kelvin")
    println("the name is ${kelvin.name}")
}

🎉 Hooray! That's dependency injection (constructor injection)! 🎉


Method Injection

Sometimes, the dependency is not ready when your object is being created. In this situation, we cannot use the Constructor Injection, and we have to use Method Injection. Let's consider an example.

We'll start with a Dinosaur class this time.

This is the class for Dinosaur. The name is not known at first, and it will be fetched from the network, so it is marked as a nullable type, String?.

class Dinosaur(var name: String? = null)

In our MainActivity.kt, we create a Dinosaur, at this point, this dinosaur has no name.

class MainActivity: AppCompatActivity {
    val dinosaur = Dinosaur()

    fun onCreate() {
         setContentView(...)
    }
}

Let's pretend it looks like this:

Next, we add the code to fetch name from network:

class MainActivity: AppCompatActivity {
    val dinosaur = Dinosaur()
    
    fun onCreate() {
         setContentView(...)
         val name: String = fetchNamefromNetwork() // pretend it's a network call
         dinosaur.name = name // inject the name once we obtain from network call

         nameTextView.text = dinosaur.name
    }

    fun fetchNamefromNetwork(): String {
        Thread.sleep(1000) // fake delay to simulate network call
        return "T-Rex"
    }
}

You can see in the code above that, at the beginning, dinosaur is instantiated with no name. And after the name is obtained from the network, then only we update the dinosaur.name.

This is method injection.

Here's the T-Rex 🎉

When to use Method Injection?

If the dependency is not ready at the time we create the object. In this example, when we create Dinosaur, name is not ready, so it's suitable for this case.

this is a trivial example to explain the point, in practical case, we can create Dinosaur object only after name is obtained.

The downside of this method is that we need to make sure that the dependency is ready when we use it. In our Dinosaur example, name is marked as nullable, so we will always have to check before using it.

dinosaur.name?.let { nonNullName ->
    // use nonNullName
}

If it is possible, constructor injection should be used to simplify your code.


You should have enough understanding to get started with Dagger 2.

Stay tuned for the next post!

Tan Jun Rong avatar
Written By

Tan Jun Rong

Android Programmer who likes writing blogs, reading, coffee, snowboarding.
Published in Android
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.