Using Dagger 2 with Android Dagger 2 Support library

We will build a simple Android application. The main Screen will have a button, this button will launch a new screen, and the new screen will display a github account name and it’s creation date.

The app will use

  • The new programming language for Android, Kotlin.
  • The MVVM pattern.
  • Daggger 2 with Android dagger 2 support library for dependency injection.
  • Android architecture component, ViewModel and LiveData.
  • Retrofit 2 for making API requests.
  • RxJava 2 for managing background tasks.

App gradle dependencies

// dagger 2 for dependency injection
implementation "com.google.dagger:dagger:2.11"
implementation "com.google.dagger:dagger-android:2.11"
implementation "com.google.dagger:dagger-android-support:2.11"
kapt "com.google.dagger:dagger-compiler:2.11"
kapt "com.google.dagger:dagger-android-processor:2.11"

// android architecture component library
implementation 'android.arch.lifecycle:extensions:1.0.0-rc1'

// rxandroid
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

// Retrofit 2 for network tasks
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
implementation 'com.google.code.gson:gson:2.8.2'

The data model

GithubAccount.kt

data class GithubAccount(
        @SerializedName("login") var login : String = "",
        @SerializedName("id") var id : Int = 0,
        @SerializedName("avatar_url") var avatarUrl : String = "",
        @SerializedName("created_at") var createdAt : String = "",
        @SerializedName("updated_at") var updatedAt : String = "") {

    override fun equals(obj: Any?): Boolean {
        return login == (obj as GithubAccount).login
    }
}

GithubApi.kt

import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
interface GithubApi {
    @GET("/users/{username}")
    fun getGithubAccountObservable(@Path("username") username: String): Observable<Response<GithubAccount>>
}

The ViewModels

GithubActivityViewModel.kt, this is empty, it’s just there for demonstration purpose.

import android.arch.lifecycle.ViewModel
import javax.inject.Inject

class GithubActivityViewModel
@Inject constructor() : ViewModel() {
}

GithubFragmentViewModel.kt, the githubApi dependency will be injected by Dagger 2. The ViewModel will be initialized in the onViewCreated function by viewModel = ViewModelProviders.of(this, viewModelFactory).get(GithubFragmentViewModel::class.java!!). The onCleared() function will be called by Android System when the fragment is destroyed.

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import android.util.Log
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableObserver
import io.reactivex.schedulers.Schedulers
import retrofit2.Response
import javax.inject.Inject

class GithubFragmentViewModel
@Inject constructor(private val githubApi: GithubApi) : ViewModel() {
    private val compositeDisposable = CompositeDisposable()
    var githubAccount = MutableLiveData<GithubAccount>()

    internal fun fetchGithubAccountInfo(username: String) {
        val disposable = githubApi.getGithubAccountObservable(username)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object : DisposableObserver<Response<GithubAccount>>() {
                    override fun onNext(response: Response<GithubAccount>) {
                        githubAccount.value = response.body()
                    }

                    override fun onComplete() {}

                    override fun onError(e: Throwable) {
                        e.printStackTrace()
                    }
                })
        compositeDisposable.add(disposable)
    }

    // This is called by the Android Activity when the activity is destroyed
    override fun onCleared() {
        Log.d("GithubActivityViewModel", "onCleared()")
        compositeDisposable.dispose()
        super.onCleared()
    }
}

The Dependency Injection Using Dagger 2 and Android Dagger 2 support library

ViewModelFactory.kt, this is a utility class for creating ViewModels so the ViewModels above can be created using the ViewModelProviders and they can be aware of the lifecycle of Activities or Fragments.

import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider

class ViewModelFactory
@Inject constructor(val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>)
    : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        var creator: Provider<ViewModel>? = creators[modelClass]

        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }

        if (creator == null) throw IllegalArgumentException("Unknown model class " + modelClass)

        @Suppress("UNCHECKED_CAST")
        try {
            return creator.get() as T
        } catch (e : Exception) {
            throw RuntimeException(e)
        }

    }
}

ViewModelKey.kt, this is for binding ViewModels in the ViewModelModules.kt

import dagger.MapKey
import kotlin.reflect.KClass
import android.arch.lifecycle.ViewModel

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

NetworkModule.kt, this provides all the things needed for making the REST API calls using Retrofit 2

import android.content.Context
import dagger.Module
import dagger.Provides
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
class NetworkModule {

    @Provides
    @Singleton
    fun provideCache(context: Context): Cache {
        val cacheSize = 10 * 1024 * 1024 // 10 MB
        val httpCacheDirectory = File(context.getCacheDir(), "http-cache")
        return Cache(httpCacheDirectory, cacheSize.toLong())
    }

    @Provides
    @Singleton
    fun provideCacheInterceptor(): Interceptor {
        return Interceptor { chain ->
            val response = chain.proceed(chain.request())

            var cacheControl = CacheControl.Builder()
                    .maxAge(1, TimeUnit.MINUTES)
                    .build()

            response.newBuilder()
                    .header("Cache-Control", cacheControl.toString())
                    .build()
        }
    }

    @Provides
    @Singleton
    fun provideHttpClient(cache : Cache, networkCacheInterceptor : Interceptor) : OkHttpClient {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        return OkHttpClient.Builder()
                .cache(cache)
                .addNetworkInterceptor(networkCacheInterceptor)
                .addInterceptor(loggingInterceptor)
                .build()

    }

    @Provides
    @Singleton
    internal fun provideRetrofit(httpClient : OkHttpClient): Retrofit {
        return Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClient)
                .build()

    }

    @Provides
    @Singleton
    internal fun provideGithubApi(retrofit: Retrofit): GithubApi {
        return retrofit.create(GithubApi::class.java)
    }

}

ViewModelModule.kt, this provides the ViewMovels for the Views.

import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap

@Module
abstract class ViewModelModule {
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory) : ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(GithubActivityViewModel::class)
    abstract fun bindGithubActivityViewModel(mainActivityViewModel: GithubActivityViewModel) : ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(GithubFragmentViewModel::class)
    abstract fun bindGithubFragmentViewModel(githubFragmentViewModel: GithubFragmentViewModel) : ViewModel

}

BuildActivityModule.kt, this generates AndroidInjector for Activities defined in this file, so things can be injected into Activities using AndroidInjection.inject(this) in the onCreate function.

import dagger.Module
import dagger.android.ContributesAndroidInjector

@Module
abstract class BuildActivityModule {
    @ContributesAndroidInjector(modules = arrayOf(BuildFragmentModule::class))
    abstract fun contributeLoginActivity() : GithubActivity
}

BuildFragmentModule.kt, this generates AndroidInjector for Fragments defined in this file, so things can be injected into Fragments using AndroidSupportInjection.inject(this) in the onAttach function.

import dagger.Module
import dagger.android.ContributesAndroidInjector

@Module
abstract class BuildFragmentModule {
    @ContributesAndroidInjector
    abstract fun contributeGithubFragment() : GithubFragment
}

AppModule.kt, this provides the Android application context.

import android.content.Context
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module(includes = arrayOf(ViewModelModule::class, NetworkModule::class))
class AppModule {
    @Provides
    @Singleton
    fun provideContext(application : MyApp): Context {
        return application.applicationContext
    }
}

AppComponent.kt, this binds all of the above together, and it will be initialized in the MyApp.kt

import dagger.Component
import dagger.android.AndroidInjector
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton

@Singleton
@Component(modules = arrayOf(
        AndroidSupportInjectionModule::class,
        AppModule::class,
        BuildActivityModule::class,
        BuildFragmentModule::class))
interface AppComponent : AndroidInjector<MyApp> {
    @Component.Builder
    abstract class Builder: AndroidInjector.Builder<MyApp>()
}

The View

MyApp.kt, with the above dagger 2 in place, after a clean and rebuild of the project, the compiler will generated a class DaggerAppComponent. It takes the AppComponent class name and prepend Dagger to it. DaggerAppComponent can then be used to create the AndroidInjector for dependency injections in Activities, Fragments, Services, BroadcastReceiver, etc. Take a look at the DaggerApplication which this MyApp is extending from.

import dagger.android.AndroidInjector
import dagger.android.DaggerApplication

class MyApp : DaggerApplication() {

    companion object {
        var instance: MyApp? = null
            private set
    }
    
    override fun applicationInjector() : AndroidInjector<MyApp> =
            DaggerAppComponent.builder().create(this@MyApp)

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

}

MainActivity.kt, this is just an activity with a button to launch another activity.

import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun launchGithub(v: View) {
        startActivity(Intent(this, GithubActivity::class.java))
    }

}

GithubActivity.kt, this is just an activity which hosts a fragment.

import android.arch.lifecycle.ViewModelProvider
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject

class GithubActivity : AppCompatActivity(), HasSupportFragmentInjector {

    @Inject lateinit var viewModelFactory: ViewModelProvider.Factory
    @Inject lateinit var dispatchingAndroidJnjector : DispatchingAndroidInjector<Fragment>

    lateinit var viewModel : GithubActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_github)

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(GithubActivityViewModel::class.java)
        supportFragmentManager
                .beginTransaction()
                .replace(R.id.fragment_container, GithubFragment.newInstance())
                .commit()
    }

    override fun supportFragmentInjector(): AndroidInjector<Fragment> {
        return dispatchingAndroidJnjector
    }
}

GithubFragment.kt, this displays the github login name and it’s account creation datetime. Dependency injections are hooked up by AndroidSupportInjection.inject(this) in the onAttach().

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProvider
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_github.*
import javax.inject.Inject

class GithubFragment : Fragment() {

    @Inject lateinit var viewModelFactory: ViewModelProvider.Factory

    lateinit var viewModel : GithubFragmentViewModel

    companion object {
        fun newInstance() : GithubFragment {
            return GithubFragment()
        }
    }

    override fun onAttach(context: Context?) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        var v = inflater.inflate(R.layout.fragment_github, container, false)
        return v
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(GithubFragmentViewModel::class.java!!)

        initObserver()

        viewModel.fetchGithubAccountInfo("google")
    }

    private fun initObserver() {
        val gitHubAccountObserver =
                Observer<GithubAccount> { githubAccount ->
                    tv_account_info.text = githubAccount!!.login + "\n" + githubAccount.createdAt
                }

        viewModel.githubAccount.observe(this, gitHubAccountObserver)
    }
}

activity_main.xml, they layout file for main activity.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Github"
        android:gravity="center"
        android:onClick="launchGithub"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

activity_github.xml, the layout file for github activity.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></FrameLayout>
</android.support.constraint.ConstraintLayout>

fragment_github.xml, the layout file for the github fragment.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_account_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

Lastly, remember to

Add this at the top of the app gradle file

apply plugin: 'kotlin-kapt'

Add this in the Manifest file

<uses-permission android:name="android.permission.INTERNET"/>

Add the MyApp as the name for application tag in the Manifest file.

Register the GithubActivty in the Manifest file.

References:

https://github.com/google/dagger
https://proandroiddev.com/exploring-the-new-dagger-android-module-9eb6075f1a46
https://proandroiddev.com/dagger-2-annotations-binds-contributesandroidinjector-a09e6a57758f
https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-2-di-1a6b1f96d84b
https://stackoverflow.com/questions/44270577/android-lifecycle-library-viewmodel-using-dagger-2

Search within Codexpedia

Custom Search

Search the entire web

Custom Search