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> }
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() internal fun fetchGithubAccountInfo(username: String) { val disposable = githubApi.getGithubAccountObservable(username) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(object : DisposableObserver >() { override fun onNext(response: Response ) { 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, @JvmSuppressWildcards Provider >) : ViewModelProvider.Factory { override fun create(modelClass: Class ): T { var creator: Provider ? = 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)
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{ @Component.Builder abstract class Builder: AndroidInjector.Builder () }
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= 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 : DispatchingAndroidInjectorlateinit 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 { 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 -> tv_account_info.text = githubAccount!!.login + "\n" + githubAccount.createdAt } viewModel.githubAccount.observe(this, gitHubAccountObserver) } }
activity_main.xml, they layout file for main activity.
activity_github.xml, the layout file for github activity.
fragment_github.xml, the layout file for the github fragment.
Lastly, remember to
Add this at the top of the app gradle file
apply plugin: 'kotlin-kapt'
Add this in the Manifest file
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
Search the entire web