GETTING STARTED TO DAGGER 2 WITH KOTLIN FOR ANDROID APP DEVELOPMENT

Mahmoud Ramadan
10 min readAug 18, 2020

Dagger is one of the most popular Dependency Injection Libraries for Java Programming Language and for Android App Development also. Because of the popularity of Kotlin as a new official language for developing Android Apps, I will use Kotlin in this tutorial so I hope you are familiar with it. After finishing this tutorial you will be able to understand the following

  • What is Dependency Injection(DI)
  • Why we need DI
  • Introduction to Dagger 2 library
  • Understanding the types of DI
  • Understanding Dagger Component

Prerequisites

To be able to get most out of this tutorial you will need:

  • Android Studio 3.2.1 or higher
  • Emulator of phone
  • Kotlin Basics
  • Intermediate level as Android Developer

Clone the Repo from Github

What is Dependency Injection(DI)

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A “dependency” is an object that can be used, for example as a service

Wikipedia

I know you may not understand this definition correctly so let me explain more in detail. In the beginning, I need you to understand the dependency meaning before understanding DI. Let me give you an example form our real life when we say you rely on transportation means to reach to your work this means you are dependent on the transportation you need them and without transportation, you can not be able to go to your work so in this case you are a dependent (Client) and transportation is your dependency (Service).

Now let us go back to our software Domain when we have class A that uses some methods form another class B so class A is a client and class B is a service. So DI creates your dependencies in someplace and returns your dependencies to you when you need them it is like magic.

Why we need DI

This is a good question in fact and I am here today to give you some thoughts I think it will convince you to use DI. I know you may face hard times when you try to implement DI in your project to be specific implement Dagger 2 in your Android Application but let me ask you a question and be honest with yourself, the question is Did you really understand the DI and How Dagger 2 works for example???

Now I will tell you why I need to use DI in the following points

  • Resolve the tight coupling between components
  • Reuse some components and modules
  • Optimize the heap memory using scopes
  • Architect your app into separated components
  • Facilitate the unit testing

Understanding DI with Simple Example

The idea is very simple we need to create a login module in our app where the user can log in through username and password for example and if it is successful we will save the username in shared preference.

  • Create a new Android Studio project with Kotlin support
  • create a new Package called di
  • create a new class called Login Manager inside di package
  • create a new class called Local Store inside di package
class LocalStore {
}
  • create a new class called ApiService inside di package
class ApiService {
}
  • Add a reference from LocalStore and ApiService in Login Manager class
class LoginManager {
val localStore = LocalStore()
val apiService = ApiService()
}

Now we need to pass the username and password for API and also save Token in shared preference so we will modify our classes to be like the following

class APiService (val username : String  , val password: String ){
}
class LocalStore(val token : String){
}

Now let us go back to our LoginManager class you will notice the error and this happens because you make changes on its dependencies LocalStore and ApiService classes. To fix this issue you need to modify your LoginManager to be like this

class LoginManager(val username : String  , val password: String ,val token : String ){
val localStore = LocalStore(token)
val apiService =ApiService(username , password)
}

Now we fixed the issue congrats!! but Do you think this is the best solution for your problem?, I think NO because if I need to change anything in the login manager‘s dependencies I need to change the login manager itself and this is called TIGH COUPLING.

So the simple fix for this issue is to pass the dependencies into login manager’s constructor like this

class LoginManager(localStore , apiService){}

Understanding the Types of DI

we have three types of dependency injection

  • Constructor Injection
  • Field Injection
  • Method Injection

Introduction to Dagger 2 library

Dagger 2 is one of the famous DI libraries among the Android Community since it is developed by Google. It is a forked project from Dagger 1 which introduced by Square. Dagger 2 does its work on Compile time effectively using Annotation Processing. Dagger has a lot of interesting annotations like Component, Subcomponent, Module, Scope, Bind. As I said before Dagger is Java DI framework so you can use it for enabling DI for your Java APPS also for Android, but recently we have the Dagger-Android version. It is optimized for Android APPS in particular, and it saves a lot of boilerplate code, but I would like to pick up the pure Dagger first.

Notice: we are going to use Dagger 2 not Dagger2-android

Add Dagger to your Android Application

Let us go to Android Studio then open Build.gradle for app module and add the following lines

api 'com.google.dagger:dagger:2.22'
kapt 'com.google.dagger:dagger-compiler:2.22'

and add kapt plugin on the top of the same file

apply plugin:  'kotlin-kapt'

Then sync and congrats you did it WOW!!!!

Dagger 2 Component

Before Talking about Dagger 2 Component I need to go back to the previous example to show you why we need a component.

Okay, let us open our MainActivity.kt

package com.daggerudemy
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.daggerudemy.di.ApiService
import com.daggerudemy.di.LocalStore
import com.daggerudemy.di.LoginManager
import com.daggerudemy.di.component.DaggerLoginComponent
import javax.inject.Inject
import kotlin.math.log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Let’s instantiate an object from our LoginManager and its dependencies and see what happens

package com.daggerudemy
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.daggerudemy.di.ApiService
import com.daggerudemy.di.LocalStore
import com.daggerudemy.di.LoginManager
import com.daggerudemy.di.component.DaggerLoginComponent
import javax.inject.Inject
import kotlin.math.log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val localStore = LocalStore ("bdjdhjdhfhfdjhd87878787d78d7")
val apiService = ApiService("ramadan","123")
val manager = LoginManager(localStore , apiStore)
}
}

if you look carefully you will notice a boilerplate code and if you need to modify the local store to hold another object, for example, you will need also to create it first then pass it to the local store and this is a hassle. I just need instance form login manager and that’s so we need something to abstract this creation and manage dependencies efficiently and here Component comes to the stage let’s give it a big hand

The component is just a bridge between the client which is MainActivity in this case and the service which is LoginManager and its function just to give the requester what needs when he asks.

How to create a Component

to create a component in Dagger 2 you need to define a new interface and your methods but you must annotate this interface with @Component.Let’s add new package called component under di package in our project. Under this package, I will create a new interface called LoginComponent and I will add @Component annotation on the top of this interface like the following:

@Component
interface LoginComponent {
fun getLoginManager() :LoginManager
}

Now let’s make constructor Injection for our LoginManager class and add login method within it like the following:

package com.daggerudemy.di
import android.util.Log
import javax.inject.Inject
class LoginManager @Inject constructor(private val localStore: LocalStore , private val apiService: ApiService){
fun login(username : String , pass:String){
Log.d("LoginManager","login($username , $pass)")
val token = apiService.authenticate(username,pass)
localStore.saveUserToken(token)
}
}

@Inject annotation means I need to create an instance from this class by passing objects to its constructor from another place. Now we need to make constructor Injection for login manager’s dependencies also like the following:

package com.daggerudemy.di
import android.util.Log
import javax.inject.Inject
class LocalStore @Inject constructor(){
fun saveUserToken(token: String) {
Log.d("LocalStore","saveUserToken($token)")
}
}
package com.daggerudemy.di
import android.util.Log
import javax.inject.Inject
class ApiService @Inject constructor() {
fun authenticate(username: String, pass: String): String {
Log.d("ApiService", "authenticate($username , $pass)")
return "wxydldklkd78dsnjuudiiudf"
}
}

Now go to Build menu in the toolbar and click on MakeProject, Dagger begins to create your dependencies in the background and your component now is ready to use. Let’s go back to the MainActivity class and remove the previous code and make an instance from your new login component like the following:

package com.daggerudemy
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.daggerudemy.di.ApiService
import com.daggerudemy.di.LocalStore
import com.daggerudemy.di.LoginManager
import com.daggerudemy.di.component.DaggerLoginComponent
import javax.inject.Inject
import kotlin.math.log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loginComponent = DaggerLoginComponent.create()
loginComponent.getLoginManager().login("ramadan","123")
}
}

Now run the app and see the log

2019-10-06 13:18:06.491 7261-7261/com.daggerudemy D/LoginManager: login(ramadan , 123)
2019-10-06 13:18:06.491 7261-7261/com.daggerudemy D/ApiService: authenticate(ramadan , 123)
2019-10-06 13:18:06.491 7261-7261/com.daggerudemy D/LocalStore: saveUserToken(wxydldklkd78dsnjuudiiudf)

Now let’s see the generated code for our component to understand DI well.

// Generated by Dagger (https://google.github.io/dagger).
package com.daggerudemy.di.component;
import com.daggerudemy.di.ApiService;
import com.daggerudemy.di.LocalStore;
import com.daggerudemy.di.LoginManager;
public final class DaggerLoginComponent implements LoginComponent {
private DaggerLoginComponent() {}
public static Builder builder() {
return new Builder();
}
public static LoginComponent create() {
return new Builder().build();
}
@Override
public LoginManager getLoginManager() {
return new LoginManager(new LocalStore(), new ApiService());
}
public static final class Builder {
private Builder() {}
public LoginComponent build() {
return new DaggerLoginComponent();
}
}
}

As you can see the Java generated DaggerLoginComponnet implements the LoginComponnet Interface and instantiate instance from LoginManager and there is Builder class with build method which returns an object from DaggerLoginComponnet, Simple isn’t it?. Now let’s improve the readability of our code to be like this

package com.daggerudemy
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.daggerudemy.di.LoginManager
import com.daggerudemy.di.component.DaggerLoginComponent
class MainActivity : AppCompatActivity() {
private val loginManager: LoginManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loginComponent = DaggerLoginComponent.create()
loginManager?.login("ramadan", "123")
}
}

It is a simple enhancement we just make a private nullable object from LoginManager and make safe calls login on it right, but the power here is we are able to make a private object from our class and this may be very important for your use case, unlike Field Injection.

Field Injection

Until now we are dealing with Constructor Injection so what about the second type of DI (Field Injection).Well, Field Injection is very useful in some situations like when we deal with third party classes and we do not own them like Picasso, Okhttp,..etc or classes that implement some interface. Let’s see Field Injection in Action right now. Please go to your MainActivity.kt and LoginComponent.kt and make the following changes

package com.daggerudemy
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.daggerudemy.di.LoginManager
import com.daggerudemy.di.component.DaggerLoginComponent
import javax.inject.Inject
class MainActivity : AppCompatActivity() {
@Inject
lateinit var loginManager: LoginManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loginComponent = DaggerLoginComponent.create()
loginComponent.inject(this)
loginManager.login("ramadan", "123")
}
}
package com.daggerudemy.di.component
import com.daggerudemy.MainActivity
import dagger.Component
@Component
interface LoginComponent {
fun inject(mainActivity: MainActivity)
}

As you can see we changed the private nullable instance of LoginManager to be public lateinit var with @Inject annotation because we can not inject private instance in Field Injection and this is one of Field Injection’s Problems. Also, we modified the LoginComponent through adding a new method called inject that takes the client or the MainActivity as a client and it inject the login manager object in the activity in a simple way.

Notice: make sure to make the instance public and access the instance after calling inject(this) method.

Now let’s see the generated Java code of Login Component after the recent modification to be able to understand the DI well.

// Generated by Dagger (https://google.github.io/dagger).
package com.daggerudemy.di.component;
import com.daggerudemy.MainActivity;
import com.daggerudemy.MainActivity_MembersInjector;
import com.daggerudemy.di.ApiService;
import com.daggerudemy.di.LocalStore;
import com.daggerudemy.di.LoginManager;
public final class DaggerLoginComponent implements LoginComponent {
private DaggerLoginComponent() {}
public static Builder builder() {
return new Builder();
}
public static LoginComponent create() {
return new Builder().build();
}
private LoginManager getLoginManager() {
return new LoginManager(new LocalStore(), new ApiService());
}
@Override
public void inject(MainActivity mainActivity) {
injectMainActivity(mainActivity);
}
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectLoginManager(instance, getLoginManager());
return instance;
}
public static final class Builder {
private Builder() {}
public LoginComponent build() {
return new DaggerLoginComponent();
}
}
}

As you can see the newly generated code looks like the same previous one but there is a little change where we have a method called injectMainActivity(mainActivity) like the following:

private MainActivity injectMainActivity(MainActivity instance) {

MainActivity_MembersInjector.injectLoginManager(instance,getLoginManager());
return instance;
}

This method will be responsible for injecting LoginManager Instance in the MainActivity.kt by using MainActivity_MembersInjector class

// Generated by Dagger (https://google.github.io/dagger).
package com.daggerudemy;
import com.daggerudemy.di.LoginManager;
import dagger.MembersInjector;
import javax.inject.Provider;
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
private final Provider<LoginManager> loginManagerProvider;
public MainActivity_MembersInjector(Provider<LoginManager> loginManagerProvider) {
this.loginManagerProvider = loginManagerProvider;
}
public static MembersInjector<MainActivity> create(Provider<LoginManager> loginManagerProvider) {
return new MainActivity_MembersInjector(loginManagerProvider);
}
@Override
public void injectMembers(MainActivity instance) {
injectLoginManager(instance, loginManagerProvider.get());
}
public static void injectLoginManager(MainActivity instance, LoginManager loginManager) {
instance.loginManager = loginManager;
}
}

MainActivity_MembersInjector class implements an interface called MembersInjector.java which has a method called void injectMembers(T instance).

/*
* Copyright (C) 2012 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger;
/**
* Injects dependencies into the fields and methods on instances of type {@code T}. Ignores the
* presence or absence of an injectable constructor.
*
* @param <T> type to inject members of
*
* @since 2.0 (since 1.0 without the provision that {@link #injectMembers} cannot accept
* {@code null})
*/
public interface MembersInjector<T> {
/**
* Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or
* absence of an injectable constructor.
*
* <p>Whenever a {@link Component} creates an instance, it performs this injection automatically
* (after first performing constructor injection), so if you're able to let the component create
* all your objects for you, you'll never need to use this method.
*
* @param instance into which members are to be injected
* @throws NullPointerException if {@code instance} is {@code null}
*/
void injectMembers(T instance);
}

so in a simple way, you just set the login manager instance to The MainActivity Instance through injectMembers method.

Conclusion

In this tutorial, we talked about the Dependency Injection and why it is very vital for your application architecture, also we learned the different types of DI and we started to learn Dagger 2 Library and to understand the component concept. I the next tutorial we will talk about more concepts in Dagger 2 Like Module, Scopes, SubComponnet deeply. Please share this tutorial with your community.

--

--