0% found this document useful (0 votes)
17 views87 pages

7 Steps To Implement Dagger 2 in Android by Anit

The document provides a 7 step guide to implementing Dagger 2 dependency injection in an Android application. Step 1 involves adding the necessary Dagger 2 and other dependencies to the application's build.gradle file. Step 2 covers configuring Room for database access by creating a MovieEntity data class. The full guide continues with steps for creating modules and components, injecting dependencies, and other implementation details. Code samples are provided in both Java and Kotlin. The GitHub repository contains a full sample app implementing the described Dagger 2 integration.

Uploaded by

crazzytimeindia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views87 pages

7 Steps To Implement Dagger 2 in Android by Anit

The document provides a 7 step guide to implementing Dagger 2 dependency injection in an Android application. Step 1 involves adding the necessary Dagger 2 and other dependencies to the application's build.gradle file. Step 2 covers configuring Room for database access by creating a MovieEntity data class. The full guide continues with steps for creating modules and components, injecting dependencies, and other implementation details. Code samples are provided in both Java and Kotlin. The GitHub repository contains a full sample app implementing the described Dagger 2 integration.

Uploaded by

crazzytimeindia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 87

Open in app Sign up Sign in

Search Write

7 steps to implement Dagger 2 in


Android
Anitaa Murthy · Follow
Published in AndroidPub · 6 min read · Dec 23, 2018

3.8K 16

3.8K 16
Image Credits: https://unsplash.com/photos/crs2vlkSe98

So I finally got a chance to explore the new Dagger 2 in a project recently. And while
there are a lot of resources online about what is Dagger and why dagger 2 is necessary
and how to implement Dagger 2, I found that there was a steep learning curve to it.
While I understood why dagger 2 is not only necessary but a must in some cases, I
found practical implementation difficult. So I thought I would write about the 7 basic
steps to implement Dagger 2 in an Android app.

Note: This article focuses on implementation of dagger using the new dagger android
injection.

I chose to build a simple movies app to test the dagger implementation. This is the
app.

This app displays a list of movies or tv shows from the famous TMDB api. There is a
details page which displays the movie or tv show videos, reviews, cast and crew info
etc. We are going to build a simplified version of this app. i.e. we are going to focus on
building a single screen app that fetches a list of movies from the TMDB api and
displays it in the app.

For those of you interested in skipping the article, the GitHub link is for this
implementation is here.

The master branch — code in Java

The kotlin_support branch — code in Kotlin

So let’s begin!

Step 1: Add the necessary dependencies to the app

Java:
1 apply plugin: 'com.android.application'
2
3 android {
4 compileSdkVersion 28
5 defaultConfig {
6 applicationId "com.an.dagger"
7 minSdkVersion 16
8 targetSdkVersion 28
9 versionCode 1
10 versionName "1.0"
11 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 }
13 buildTypes {
14 release {
15 minifyEnabled false
16 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules
17 }
18 }
19
20 /* I use Data Binding and Java8 in all my projects */
21 dataBinding {
22 enabled true
23 }
24 compileOptions {
25 sourceCompatibility JavaVersion.VERSION_1_8
26 targetCompatibility JavaVersion.VERSION_1_8
27 }
28 }
29
30 dependencies {
31 implementation fileTree(dir: 'libs', include: ['*.jar'])
32 testImplementation 'junit:junit:4.12'
33 androidTestImplementation 'com.android.support.test:runner:1.0.2'
34 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
35
36 /* Android Support Library: RecyclerView, CardView */
37 implementation 'com.android.support:appcompat-v7:28.0.0'
38 implementation 'com.android.support:recyclerview-v7:28.0.0'
39 implementation 'com.android.support:design:28.0.0'
40 implementation 'com.android.support:cardview-v7:28.0.0'
41
42 /* Retrofit using RxJava2, Okhttp, Okhttp logging interceptor, Gson */
43 implementation 'com.squareup.retrofit2:retrofit:2.4.0'
44 implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
45 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
46 implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
47
48
49 /* Picasso lib for image loading */
50 implementation 'com.squareup.picasso:picasso:2.71828'
51
52 /* Android Architecture Component - ConstraintLayout */
53 implementation 'com.android.support.constraint:constraint-layout:1.0.2'
54
55 /* Android Architecture Component - LiveData & ViewModel */
56 implementation 'android.arch.lifecycle:extensions:1.1.1'
57
58 /* Android Architecture Component - Room Persistance Lib */
59 implementation 'android.arch.persistence.room:runtime:1.1.1'
60 implementation 'android.arch.persistence.room:rxjava2:1.1.1'
61 annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
62
63 /* Dagger2 - We are going to use dagger.android which includes
64 * support for Activity and fragment injection so we need to include
65 * the following dependencies */
66 implementation 'com.google.dagger:dagger-android:2.17'
67 implementation 'com.google.dagger:dagger-android-support:2.17'
68 annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'
69
70 /* Dagger2 - default dependency */
71 annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
72 }

build.gradle hosted with ❤ by GitHub view raw


Kotlin:
1 // Top-level build file where you can add configuration options common to all sub-projects/modules
2
3 buildscript {
4 repositories {
5 google()
6 jcenter()
7
8 }
9 dependencies {
10 classpath 'com.android.tools.build:gradle:3.4.0-alpha01'
11 classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.71'
12
13 // NOTE: Do not place your application dependencies here; they belong
14 // in the individual module build.gradle files
15 }
16 }
17
18 allprojects {
19 repositories {
20 google()
21 jcenter()
22
23 }
24 }
25
26 task clean(type: Delete) {
27 delete rootProject.buildDir
28 }

build.gradle hosted with ❤ by GitHub view raw


add kotlin support first in build.gradle
1 apply plugin: 'com.android.application'
2 apply plugin: 'kotlin-android'
3 apply plugin: 'kotlin-android-extensions'
4 apply plugin: 'kotlin-kapt'
5
6 android {
7 compileSdkVersion 28
8 defaultConfig {
9 applicationId "com.an.dagger"
10 minSdkVersion 16
11 targetSdkVersion 28
12 versionCode 1
13 versionName "1.0"
14 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 }
16 buildTypes {
17 release {
18 minifyEnabled false
19 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules
20 }
21 }
22
23 /* I use Data Binding and Java8 in all my projects */
24 dataBinding {
25 enabled true
26 }
27 compileOptions {
28 sourceCompatibility JavaVersion.VERSION_1_8
29 targetCompatibility JavaVersion.VERSION_1_8
30 }
31 }
32
33 dependencies {
34 implementation fileTree(dir: 'libs', include: ['*.jar'])
35 testImplementation 'junit:junit:4.12'
36 androidTestImplementation 'com.android.support.test:runner:1.0.2'
37 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
38
39 /* Android Support Library: RecyclerView, CardView */
40 implementation 'com.android.support:appcompat-v7:28.0.0'
41 implementation 'com.android.support:recyclerview-v7:28.0.0'
42 implementation 'com.android.support:design:28.0.0'
43 implementation 'com.android.support:cardview-v7:28.0.0'
44
45 /* Retrofit using RxJava2, Okhttp, Okhttp logging interceptor, Gson */
46 implementation 'com.squareup.retrofit2:retrofit:2.4.0'
47 implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
48 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
49 implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
50
51
52 /* Picasso lib for image loading */
53 implementation 'com.squareup.picasso:picasso:2.71828'
54
55 /* Android Architecture Component - ConstraintLayout */
56 implementation 'com.android.support.constraint:constraint-layout:1.0.2'
57
58 /* Android Architecture Component - LiveData & ViewModel */
59 implementation 'android.arch.lifecycle:extensions:1.1.1'
60
61 /* Android Architecture Component - Room Persistance Lib */
62 implementation 'android.arch.persistence.room:runtime:1.1.1'
63 implementation 'android.arch.persistence.room:rxjava2:1.1.1'
64 kapt 'android.arch.persistence.room:compiler:1.1.1'
65
66 /* Dagger2 - We are going to use dagger.android which includes
67 * support for Activity and fragment injection so we need to include
68 * the following dependencies */
69 implementation 'com.google.dagger:dagger-android:2.17'
70 implementation 'com.google.dagger:dagger-android-support:2.17'
71 kapt 'com.google.dagger:dagger-android-processor:2.17'
72
73 /* Dagger2 - default dependency */
74 kapt 'com.google.dagger:dagger-compiler:2.17'
75
76 /* Add Kotlin support */
77 implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71'
78 }

build.gradle hosted with ❤ by GitHub view raw


app/build.gradle

Step 2: Configure Room


Next thing we have to do is setup Room database in the app. This involves:

1. Creating an Entity:

MovieEntity.java:
1 @Entity(primaryKeys = ("id"))
2 public class MovieEntity implements Parcelable {
3
4 @SerializedName("id")
5 @Expose
6 private Long id;
7
8 @SerializedName(value="header", alternate={"title", "name"})
9 @Expose
10 private String header;
11
12 @SerializedName("poster_path")
13 @Expose
14 private String posterPath;
15
16 @SerializedName(value="description", alternate={"overview", "synopsis"})
17 private String description;
18
19 @SerializedName("release_date")
20 @Expose
21 private String releaseDate;
22
23 @SerializedName("runtime")
24 @Expose
25 private Long runtime;
26
27 @SerializedName("status")
28 @Expose
29 private String status;
30
31 public Long getId() {
32 return id;
33 }
34
35 public void setId(Long id) {
36 this.id = id;
37 }
38
39 public String getHeader() {
40 return header;
41 }
42
43 public void setHeader(String header) {
44 this.header = header;
45 }
46
47 public String getPosterPath() {
48 return posterPath;
49 }
50
51 public void setPosterPath(String posterPath) {
52 this.posterPath = posterPath;
53 }
54
55 public String getDescription() {
56 return description;
57 }
58
59 public void setDescription(String description) {
60 this.description = description;
61 }
62
63 public String getReleaseDate() {
64 return releaseDate;
65 }
66
67 public void setReleaseDate(String releaseDate) {
68 this.releaseDate = releaseDate;
69 }
70
71 public Long getRuntime() {
72 return runtime;
73 }
74
75 public void setRuntime(Long runtime) {
76 this.runtime = runtime;
77 }
78
79 public String getStatus() {
80 return status;
81 }
82
83 public void setStatus(String status) {
84 this.status = status;
85 }
86
87
88 @Override
89 public int describeContents() {
90 return 0;
91 }
92
93 @Override
94 public void writeToParcel(Parcel dest, int flags) {
95 dest.writeValue(this.id);
96 dest.writeString(this.header);
97 dest.writeString(this.posterPath);
98 dest.writeString(this.description);
99 dest.writeString(this.releaseDate);
100 dest.writeValue(this.runtime);
101 dest.writeString(this.status);
102 }
103
104 public MovieEntity() {
105 }
106
107 protected MovieEntity(Parcel in) {
108 this.id = (Long) in.readValue(Long.class.getClassLoader());
109 this.header = in.readString();
110 this.posterPath = in.readString();
111 this.description = in.readString();
112 this.releaseDate = in.readString();
113 this.runtime = (Long) in.readValue(Long.class.getClassLoader());
114 this.status = in.readString();
115 }
116
117 public static final Creator<MovieEntity> CREATOR = new Creator<MovieEntity>() {
118 @Override
119 public MovieEntity createFromParcel(Parcel source) {
120 return new MovieEntity(source);
121 }
122
123 @Override
124 public MovieEntity[] newArray(int size) {
125 return new MovieEntity[size];
126 }
127 };
128 }

MovieEntity.java hosted with ❤ by GitHub view raw


MovieEntity.kt:
1 @Entity(primaryKeys = ["id"])
2 data class MovieEntity(
3 @SerializedName("id")
4 val id: Long,
5
6 @SerializedName(value = "header", alternate = ["title", "name"])
7 val header: String,
8
9 @SerializedName("poster_path")
10 var posterPath: String?,
11
12 @SerializedName(value = "description", alternate = ["overview", "synopsis"])
13 var description: String?,
14
15 @SerializedName("release_date")
16 var releaseDate: String?,
17
18 @SerializedName("runtime")
19 var runTime: Long,
20 var status: String?
21 ) : Parcelable {
22
23
24 fun getFormattedPosterPath(): String? {
25 if (posterPath != null && !posterPath!!.startsWith("http")) {
26 posterPath = String.format(AppConstants.IMAGE_URL, posterPath)
27 }
28 return posterPath
29 }
30
31 constructor(source: Parcel) : this(
32 source.readLong(),
33 source.readString(),
34 source.readString(),
35 source.readString(),
36 source.readString(),
37 source.readLong(),
38 source.readString()
39 )
40
41 override fun describeContents() = 0
42
43 override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
44 writeLong(id)
45 writeString(header)
46 writeString(posterPath)
47 writeString(description)
48 writeString(releaseDate)
49 writeLong(runTime)
50 writeString(status)
51 }
52
53 companion object {
54 @JvmField
55 val CREATOR: Parcelable.Creator<MovieEntity> = object : Parcelable.Creator<MovieEntity> {
56 override fun createFromParcel(source: Parcel): MovieEntity = MovieEntity(source)
57 override fun newArray(size: Int): Array<MovieEntity?> = arrayOfNulls(size)
58 }
59 }
60 }

MovieEntity.kt hosted with ❤ by GitHub view raw


2. Creating Dao class:

MovieDao.java:

1 @Dao
2 public interface MovieDao {
3
4 @Insert(onConflict = OnConflictStrategy.REPLACE)
5 long[] insertMovies(List<MovieEntity> movies);
6
7 @Query("SELECT * FROM `MovieEntity`")
8 List<MovieEntity> getMoviesByPage();
9 }

MovieDao.java hosted with ❤ by GitHub view raw

MovieDao.kt:
1 @Dao
2 interface MovieDao {
3
4 /* Method to insert the movies fetched from api
5 * to room */
6 @Insert(onConflict = OnConflictStrategy.REPLACE)
7 fun insertMovies(movies: List<MovieEntity>): LongArray
8
9 /* Method to fetch the movies stored locally */
10 @Query("SELECT * FROM `MovieEntity`")
11 fun getMoviesByPage(): List<MovieEntity>
12 }

MovieDao.kt hosted with ❤ by GitHub view raw

3. Creating the Database:

AppDatabase.java:

1 @Database(entities = {MovieEntity.class}, version = 1, exportSchema = false)


2 public abstract class AppDatabase extends RoomDatabase {
3
4 public abstract MovieDao movieDao();
5 }

AppDatabase.java hosted with ❤ by GitHub view raw

AppDatabase.kt:
1 @Database(entities = [MovieEntity::class], version = 1, exportSchema = false)
2 abstract class AppDatabase : RoomDatabase() {
3
4 abstract fun movieDao(): MovieDao
5 }

AppDatabase.kt hosted with ❤ by GitHub view raw

Note: If you would like to know more about Room persistence Library and how to configure
it, then checkout this article

Step 3: Configure Api Service:


Now that we have setup the local database, we need to configure the api service
system. We are going to use Retrofit for that. This involves:

1. Creating a rest api response model:

MovieApiResponse.java:
1 public class MovieApiResponse {
2
3 public MovieApiResponse() {
4 this.results = new ArrayList<>();
5 }
6
7 private long page;
8
9 @SerializedName("total_pages")
10 private long totalPages;
11
12 @SerializedName("total_results")
13 private long totalResults;
14
15 private List<MovieEntity> results;
16
17 public long getPage() {
18 return page;
19 }
20
21 public void setPage(long page) {
22 this.page = page;
23 }
24
25 public long getTotalPages() {
26 return totalPages;
27 }
28
29 public void setTotalPages(long totalPages) {
30 this.totalPages = totalPages;
31 }
32
33 public long getTotalResults() {
34 return totalResults;
35 }
36
37 public void setTotalResults(long totalResults) {
38 this.totalResults = totalResults;
39 }
40
41 public List<MovieEntity> getResults() {
42 return results;
43 }
44
45 public void setResults(List<MovieEntity> results) {
46 this.results = results;
47 }
48 }

MovieApiResponse.java hosted with ❤ by GitHub view raw


MovieApiResponse.kt:

1 data class MovieApiResponse(val page: Long,


2 val results: List<MovieEntity>,
3 val total_results: Long,
4 val total_pages: Long)

MovieApiResponse.kt hosted with ❤ by GitHub view raw

2. Creating a rest api service:


MovieApiService.java:

1 public interface MovieApiService {


2
3 @GET("movie/popular?language=en-US&region=US&page=1")
4 Observable<MovieApiResponse> fetchMovies();
5 }

MovieApiService.java hosted with ❤ by GitHub view raw

MovieApiService.kt:

1 interface MovieApiService {
2
3 @GET("movie/popular?language=en-US&region=US&page=1")
4 fun fetchMoviesByType(): Observable<MovieApiResponse>
5 }

MovieApiService.kt hosted with ❤ by GitHub view raw

Step 4: Configure Repository class


The repository classes are responsible for handling data operations. For instance, the
first time the app is opened, the data will be fetched from the backend api service
and stored locally in room. But if there is no internet or the api service is down, the
data will be fetched from the local cache. So this class will choose which data sources
to use to get the data.

MovieRepository.java:
1 /*
2 * One of the first things we do in the Repository class
3 * is to make it a Singleton.
4 * */
5
6 @Singleton
7 public class MovieRepository {
8
9 private MovieDao movieDao;
10 private MovieApiService movieApiService;
11 public MovieRepository(MovieDao movieDao,
12 MovieApiService movieApiService) {
13 this.movieDao = movieDao;
14 this.movieApiService = movieApiService;
15 }
16
17
18
19 /*
20 * We are using this method to fetch the movies list
21 * NetworkBoundResource is part of the Android architecture
22 * components. You will notice that this is a modified version of
23 * that class. That class is based on LiveData but here we are
24 * using Observable from RxJava.
25 *
26 * There are three methods called:
27 * a. fetch data from server
28 * b. fetch data from local
29 * c. save data from api in local
30 *
31 * So basically we fetch data from server, store it locally
32 * and then fetch data from local and update the UI with
33 * this data.
34 *
35 * */
36 public Observable<Resource<List<MovieEntity>>> loadMoviesByType() {
37 return new NetworkBoundResource<List<MovieEntity>, MovieApiResponse>() {
38
39 @Override
40 protected void saveCallResult(@NonNull MovieApiResponse item) {
41 movieDao.insertMovies(item.getResults());
42 }
43
44 @Override
45 protected boolean shouldFetch() {
46 return true;
47 }
48
49 @NonNull
50 @Override
51 protected Flowable<List<MovieEntity>> loadFromDb() {
52 List<MovieEntity> movieEntities = movieDao.getMoviesByPage();
53 if(movieEntities == null || movieEntities.isEmpty()) {
54 return Flowable.empty();
55 }
56 return Flowable.just(movieEntities);
57 }
58
59 @NonNull
60 @Override
61 protected Observable<Resource<MovieApiResponse>> createCall() {
62 return movieApiService.fetchMovies()
63 .flatMap(movieApiResponse -> Observable.just(movieApiResponse == null
64 ? Resource.error("", new MovieApiResponse())
65 : Resource.success(movieApiResponse)));
66 }
67 }.getAsObservable();
68 }
69 }

MovieRepository.java hosted with ❤ by GitHub view raw


MovieRepository.kt:
1 /*
2 * One of the first things we do in the Repository class
3 * is to make it a Singleton.
4 * */
5
6 @Singleton
7 class MovieRepository(
8 private val movieDao: MovieDao,
9 private val movieApiService: MovieApiService
10 ) {
11
12
13 /*
14 * We are using this method to fetch the movies list
15 * NetworkBoundResource is part of the Android architecture
16 * components. You will notice that this is a modified version of
17 * that class. That class is based on LiveData but here we are
18 * using Observable from RxJava.
19 *
20 * There are three methods called:
21 * a. fetch data from server
22 * b. fetch data from local
23 * c. save data from api in local
24 *
25 * So basically we fetch data from server, store it locally
26 * and then fetch data from local and update the UI with
27 * this data.
28 *
29 * */
30 fun loadMoviesByType(): Observable<Resource<List<MovieEntity>>> {
31 return object : NetworkBoundResource<List<MovieEntity>, MovieApiResponse>() {
32
33 override fun saveCallResult(item: MovieApiResponse) {
34 movieDao.insertMovies(item.results)
35 }
36
37 override fun shouldFetch(): Boolean {
38 return true
39 }
40
41 override fun loadFromDb(): Flowable<List<MovieEntity>> {
42 val movieEntities = movieDao.getMoviesByPage()
43 return if (movieEntities == null || movieEntities.isEmpty()) {
44 Flowable.empty()
45 } else Flowable.just(movieEntities)
46 }
47
48 override fun createCall(): Observable<Resource<MovieApiResponse>> {
49 return movieApiService.fetchMoviesByType()
50 .flatMap { movieApiResponse ->
51 Observable.just(
52 if (movieApiResponse == null) Resource.error("", MovieApiRespon
53 else Resource.success(movieApiResponse)
54 )
55 }
56 }
57 }.getAsObservable()
58 }
59 }

MovieRepository.kt hosted with ❤ by GitHub view raw


Step 5: Configure the ViewModel class
As mentioned in the previous step:

The repository class is responsible for fetching the data (either from web api or
local cache).

The ViewModel is responsible for updating the UI with the data changes. The
ViewModel will initialise an instance of the Repository class and update the UI
based with this data.

For this, the ViewModel must have access to the using MovieDao class and the
MovieApiService class. This is where Dagger comes in. We will be injecting the
ViewModel with the MovieDao class and the MovieApiService class.

Now, let’s setup the ViewModel class.

MovieListViewModel.java:
1 public class MovieListViewModel extends ViewModel {
2
3 private MovieRepository movieRepository;
4
5 /* We are using LiveData to update the UI with the data changes.
6 */
7 private MutableLiveData<Resource<List<MovieEntity>>> moviesLiveData = new MutableLiveData<>();
8
9
10 /*
11 * We are injecting the MovieDao class
12 * and the MovieApiService class to the ViewModel.
13 * */
14
15 @Inject
16 public MovieListViewModel(MovieDao movieDao, MovieApiService movieApiService) {
17 /* You can see we are initialising the MovieRepository class here */
18 movieRepository = new MovieRepository(movieDao, movieApiService);
19 }
20
21
22 /*
23 * Method called by UI to fetch movies list
24 * */
25 public void loadMoreMovies() {
26 movieRepository.loadMoviesByType()
27 .subscribe(resource -> getMoviesLiveData().postValue(resource));
28 }
29
30
31 /*
32 * LiveData observed by the UI
33 * */
34 public MutableLiveData<Resource<List<MovieEntity>>> getMoviesLiveData() {
35 return moviesLiveData;
36 }
37 }

MovieListViewModel.java hosted with ❤ by GitHub view raw


MovieListViewModel.kt:
1 /*
2 * We are injecting the MovieDao class
3 * and the MovieApiService class to the ViewModel.
4 * */
5 class MovieListViewModel @Inject constructor(
6 movieDao: MovieDao,
7 movieApiService: MovieApiService) : ViewModel() {
8
9 /* You can see we are initialising the MovieRepository class here */
10 private val movieRepository: MovieRepository = MovieRepository(movieDao, movieApiService)
11
12 /* We are using LiveData to update the UI with the data changes.
13 */
14 private val moviesListLiveData = MutableLiveData<Resource<List<MovieEntity>>>()
15
16 /*
17 * Method called by UI to fetch movies list
18 * */
19 fun loadMoreMovies() {
20 movieRepository.loadMoviesByType()
21 .subscribe { resource -> getMoviesLiveData().postValue(resource) }
22 }
23
24 /*
25 * LiveData observed by the UI
26 * */
27 fun getMoviesLiveData() = moviesListLiveData
28 }

MovieListViewModel.kt hosted with ❤ by GitHub view raw


Step 5: Configure Dagger (finally!)
Now let’s create some Dagger classes.

In Dagger, we can annotate classes with @Module . These classes are responsible for
providing objects/classes which can be injected. Such classes can define methods
annotated with @ Provides . The returned objects from these methods are available
for dependency injection.

So in our case, we need to inject two classes: MovieDao class and MovieApiService

class. So we are going to create two Modules: ApiModule and DbModule (This can be
done in a single module but I prefer to keep local and web service separate).

1. ApiModule class

ApiModule.java
1 @Module
2 public class ApiModule {
3
4 /*
5 * The method returns the Gson object
6 * */
7 @Provides
8 @Singleton
9 Gson provideGson() {
10 GsonBuilder gsonBuilder = new GsonBuilder();
11 return gsonBuilder.create();
12 }
13
14
15 /*
16 * The method returns the Cache object
17 * */
18 @Provides
19 @Singleton
20 Cache provideCache(Application application) {
21 long cacheSize = 10 * 1024 * 1024; // 10 MB
22 File httpCacheDirectory = new File(application.getCacheDir(), "http-cache");
23 return new Cache(httpCacheDirectory, cacheSize);
24 }
25
26
27 /*
28 * The method returns the Okhttp object
29 * */
30 @Provides
31 @Singleton
32 OkHttpClient provideOkhttpClient(Cache cache) {
33 HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
34 logging.setLevel(HttpLoggingInterceptor.Level.BODY);
35
36 OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
37 httpClient.cache(cache);
38 httpClient.addInterceptor(logging);
39 httpClient.addNetworkInterceptor(new RequestInterceptor());
40 httpClient.connectTimeout(30, TimeUnit.SECONDS);
41 httpClient.readTimeout(30, TimeUnit.SECONDS);
42 return httpClient.build();
43 }
44
45
46 /*
47 * The method returns the Retrofit object
48 * */
49 @Provides
50 @Singleton
51 Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
52 return new Retrofit.Builder()
53 .addConverterFactory(GsonConverterFactory.create(gson))
54 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
55 .baseUrl("https://api.themoviedb.org/3/")
56 .client(okHttpClient)
57 .build();
58 }
59
60 /*
61 * We need the MovieApiService module.
62 * For this, We need the Retrofit object, Gson, Cache and OkHttpClient .
63 * So we will define the providers for these objects here in this module.
64 *
65 * */
66
67 @Provides
68 @Singleton
69 MovieApiService provideMovieApiService(Retrofit retrofit) {
70 return retrofit.create(MovieApiService.class);
71 }
72 }

ApiModule.java hosted with ❤ by GitHub view raw


ApiModule.kt
1 @Module
2 class ApiModule {
3
4 /*
5 * The method returns the Gson object
6 * */
7 @Provides
8 @Singleton
9 internal fun provideGson(): Gson {
10 val gsonBuilder = GsonBuilder()
11 return gsonBuilder.create()
12 }
13
14
15 /*
16 * The method returns the Cache object
17 * */
18 @Provides
19 @Singleton
20 internal fun provideCache(application: Application): Cache {
21 val cacheSize = (10 * 1024 * 1024).toLong() // 10 MB
22 val httpCacheDirectory = File(application.cacheDir, "http-cache")
23 return Cache(httpCacheDirectory, cacheSize)
24 }
25
26
27 /*
28 * The method returns the Okhttp object
29 * */
30 @Provides
31 @Singleton
32 internal fun provideOkhttpClient(cache: Cache): OkHttpClient {
33 val logging = HttpLoggingInterceptor()
34 logging.level = HttpLoggingInterceptor.Level.BODY
35
36 val httpClient = OkHttpClient.Builder()
37 httpClient.cache(cache)
38 httpClient.addInterceptor(logging)
39 httpClient.addNetworkInterceptor(RequestInterceptor())
40 httpClient.connectTimeout(30, TimeUnit.SECONDS)
41 httpClient.readTimeout(30, TimeUnit.SECONDS)
42 return httpClient.build()
43 }
44
45
46 /*
47 * The method returns the Retrofit object
48 * */
49 @Provides
50 @Singleton
51 internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
52 return Retrofit.Builder()
53 .addConverterFactory(GsonConverterFactory.create(gson))
54 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
55 .baseUrl("https://api.themoviedb.org/3/")
56 .client(okHttpClient)
57 .build()
58 }
59
60
61 /*
62 * We need the MovieApiService module.
63 * For this, We need the Retrofit object, Gson, Cache and OkHttpClient .
64 * So we will define the providers for these objects here in this module.
65 *
66 * */
67 @Provides
68 @Singleton
69 internal fun provideMovieApiService(retrofit: Retrofit): MovieApiService {
70 return retrofit.create(MovieApiService::class.java)
71 }
72 }

ApiModule.kt hosted with ❤ by GitHub view raw


2. DbModule class

DbModule.java
1 @Module
2 public class DbModule {
3
4 /*
5 * The method returns the Database object
6 * */
7 @Provides
8 @Singleton
9 AppDatabase provideDatabase(@NonNull Application application) {
10 return Room.databaseBuilder(application,
11 AppDatabase.class, "Entertainment.db")
12 .allowMainThreadQueries().build();
13 }
14
15
16 /*
17 * We need the MovieDao module.
18 * For this, We need the AppDatabase object
19 * So we will define the providers for this here in this module.
20 * */
21
22 @Provides
23 @Singleton
24 MovieDao provideMovieDao(@NonNull AppDatabase appDatabase) {
25 return appDatabase.movieDao();
26 }
27 }

DbModule.java hosted with ❤ by GitHub view raw


DbModule.kt

1 @Module
2 class DbModule {
3
4 /*
5 * The method returns the Database object
6 * */
7 @Provides
8 @Singleton
9 internal fun provideDatabase(application: Application): AppDatabase {
10 return Room.databaseBuilder(
11 application, AppDatabase::class.java, "Entertainment.db")
12 .allowMainThreadQueries().build()
13 }
14
15
16 /*
17 * We need the MovieDao module.
18 * For this, We need the AppDatabase object
19 * So we will define the providers for this here in this module.
20 * */
21 @Provides
22 @Singleton
23 internal fun provideMovieDao(appDatabase: AppDatabase): MovieDao {
24 return appDatabase.movieDao()
25 }
26 }

DbModule.kt hosted with ❤ by GitHub view raw


3. ViewModelFactory class:
Now we need to inject these two modules into our ViewModel . ViewModelFactory class
basically helps you dynamically create ViewModels for your Activities and Fragments.
The ViewModelFactory class has a list of providers and can create any ViewModel that
was bound. Fragments and Activities can now just inject the factory and retrieve their
ViewModel .

ViewModelFactory.java
1 @Singleton
2 public class ViewModelFactory implements ViewModelProvider.Factory {
3
4 private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
5
6 @Inject
7 public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
8 this.creators = creators;
9 }
10
11 @Override
12 public <T extends ViewModel> T create(Class<T> modelClass) {
13 Provider<? extends ViewModel> creator = creators.get(modelClass);
14 if (creator == null) {
15 for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entryS
16 if (modelClass.isAssignableFrom(entry.getKey())) {
17 creator = entry.getValue();
18 break;
19 }
20 }
21 }
22 if (creator == null) {
23 throw new IllegalArgumentException("unknown model class " + modelClass);
24 }
25 try {
26 return (T) creator.get();
27 } catch (Exception e) {
28 throw new RuntimeException(e);
29 }
30 }
31 }

ViewModelFactory.java hosted with ❤ by GitHub view raw


ViewModelFactory.kt

1 @Singleton
2 class ViewModelFactory @Inject
3 constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewMo
4
5 override fun <T : ViewModel> create(modelClass: Class<T>): T {
6 val creator = viewModels[modelClass]
7 ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
8 ?: throw IllegalArgumentException("unknown model class $modelClass")
9 return try {
10 creator.get() as T
11 } catch (e: Exception) {
12 throw RuntimeException(e)
13 }
14 }
15 }

ViewModelFactory.kt hosted with ❤ by GitHub view raw

4. ViewModelKey class:
ViewModelKeys helps you map your ViewModel classes so ViewModelFactory can
correctly provide/inject them.

ViewModelKey.java
1 @Target({ElementType.METHOD})
2 @Retention(RetentionPolicy.RUNTIME)
3 @MapKey
4 public @interface ViewModelKey {
5 Class<? extends ViewModel> value();
6 }

ViewModelKey.java hosted with ❤ by GitHub view raw

ViewModelKey.kt

1 @Target(AnnotationTarget.FUNCTION,
2 AnnotationTarget.PROPERTY_GETTER,
3 AnnotationTarget.PROPERTY_SETTER)
4 @MapKey
5 annotation class ViewModelKey(val value: KClass<out ViewModel>)

ViewModelKey.kt hosted with ❤ by GitHub view raw

4. ViewModelModule class
The ViewModelModule is used to provide a map of view models through dagger that is
used by the ViewModelFactory class.

ViewModelModule.java
1 @Module
2 public abstract class ViewModelModule {
3
4 @Binds
5 abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
6
7
8 /*
9 * This method basically says
10 * inject this object into a Map using the @IntoMap annotation,
11 * with the MovieListViewModel.class as key,
12 * and a Provider that will build a MovieListViewModel
13 * object.
14 *
15 * */
16
17 @Binds
18 @IntoMap
19 @ViewModelKey(MovieListViewModel.class)
20 protected abstract ViewModel movieListViewModel(MovieListViewModel moviesListViewModel);
21 }

ViewModelModule.java hosted with ❤ by GitHub view raw

ViewModelModule.kt
1 @Module
2 internal abstract class ViewModelModule {
3
4 @Binds
5 internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Facto
6
7
8 /*
9 * This method basically says
10 * inject this object into a Map using the @IntoMap annotation,
11 * with the MovieListViewModel.class as key,
12 * and a Provider that will build a MovieListViewModel
13 * object.
14 *
15 * */
16
17 @Binds
18 @IntoMap
19 @ViewModelKey(MovieListViewModel::class)
20 protected abstract fun movieListViewModel(moviesListViewModel: MovieListViewModel): ViewModel
21 }

ViewModelModule.kt hosted with ❤ by GitHub view raw

So basically,

We can use the ViewModelModule to define our ViewModels .

We provide a key for each ViewModel using the ViewModelKey class.

Then in our Activity/Fragment, we use the ViewModelFactory class to inject the


corresponding ViewModel . (We will look into more detail at this when we are
creating our Activity).

5. ActivityModule class
Since we are using the dagger-android support library, we can make use of Android
Injection. The ActivityModule generates AndroidInjector (this is the new dagger-
android class which exist in dagger-android framework) for Activities defined in this
class. This allows us to inject things into Activities using
AndroidInjection.inject(this) in the onCreate() method.

ActivityModule.java

1 @Module
2 public abstract class ActivityModule {
3
4 @ContributesAndroidInjector()
5 abstract MainActivity contributeMainActivity();
6 }

ActivityModule.java hosted with ❤ by GitHub view raw

ActivityModule.kt

1 @Module
2 abstract class ActivityModule {
3
4 @ContributesAndroidInjector()
5 abstract fun contributeMainActivity(): MainActivity
6 }

ActivityModule.kt hosted with ❤ by GitHub view raw


Note: We can define all of the Activity which require injecting. For instance, in our case,
this will generate AndroidInjector<MainActivity> .

6. AppComponent class
Any class with the annotation @Component defines the connection between the
modules and the classes which need the dependency. We define a @Component.Builder

interface which will be called from our custom Application class. This will set our
application object to the AppComponent . So inside the AppComponent the application
instance is available. So this application instance can be accessed by our modules
such as ApiModule when needed.

AppComponent.java
1 /*
2 * We mark this interface with the @Component annotation.
3 * And we define all the modules that can be injected.
4 * Note that we provide AndroidSupportInjectionModule.class
5 * here. This class was not created by us.
6 * It is an internal class in Dagger 2.10.
7 * Provides our activities and fragments with given module.
8 * */
9 @Component(modules = {
10 ApiModule.class,
11 DbModule.class,
12 ViewModelModule.class,
13 ActivityModule.class,
14 AndroidSupportInjectionModule.class})
15 @Singleton
16 public interface AppComponent {
17
18
19 /* We will call this builder interface from our custom Application class.
20 * This will set our application object to the AppComponent.
21 * So inside the AppComponent the application instance is available.
22 * So this application instance can be accessed by our modules
23 * such as ApiModule when needed
24 * */
25 @Component.Builder
26 interface Builder {
27
28 @BindsInstance
29 Builder application(Application application);
30
31 AppComponent build();
32 }
33
34
35 /*
36 * This is our custom Application class
37 * */
38 void inject(AppController appController);
39 }

AppComponent.java hosted with ❤ by GitHub view raw


AppComponent.kt
1 /*
2 * We mark this interface with the @Component annotation.
3 * And we define all the modules that can be injected.
4 * Note that we provide AndroidSupportInjectionModule.class
5 * here. This class was not created by us.
6 * It is an internal class in Dagger 2.10.
7 * Provides our activities and fragments with given module.
8 * */
9 @Component(
10 modules = [
11 ApiModule::class,
12 DbModule::class,
13 ViewModelModule::class,
14 ActivityModule::class,
15 AndroidSupportInjectionModule::class]
16 )
17 @Singleton
18 interface AppComponent {
19
20 /*
21 * We will call this builder interface from our custom Application class.
22 * This will set our application object to the AppComponent.
23 * So inside the AppComponent the application instance is available.
24 * So this application instance can be accessed by our modules
25 * such as ApiModule when needed
26 *
27 * */
28 @Component.Builder
29 interface Builder {
30 @BindsInstance
31 fun application(application: Application): Builder
32
33 fun build(): AppComponent
34 }
35
36 /*
37 * This is our custom Application class
38 * */
39 fun inject(appController: AppController)
40 }

AppComponent.kt hosted with ❤ by GitHub view raw


Step 6: Configure Application class
So now we create a custom Application class in our project.

AppController.java
1 /*
2 * we use our AppComponent (now prefixed with Dagger)
3 * to inject our Application class.
4 * This way a DispatchingAndroidInjector is injected which is
5 * then returned when an injector for an activity is requested.
6 * */
7 public class AppController extends Application implements HasActivityInjector {
8
9 @Inject
10 DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
11
12 @Override
13 public DispatchingAndroidInjector<Activity> activityInjector() {
14 return dispatchingAndroidInjector;
15 }
16
17 @Override
18 public void onCreate() {
19 super.onCreate();
20 DaggerAppComponent.builder()
21 .application(this)
22 .build()
23 .inject(this);
24 }
25 }

AppController.java hosted with ❤ by GitHub view raw

AppController.kt
1 /*
2 * we use our AppComponent (now prefixed with Dagger)
3 * to inject our Application class.
4 * This way a DispatchingAndroidInjector is injected which is
5 * then returned when an injector for an activity is requested.
6 * */
7 class AppController : Application(), HasActivityInjector {
8
9 @Inject
10 lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
11
12 override fun activityInjector(): DispatchingAndroidInjector<Activity>? {
13 return dispatchingAndroidInjector
14 }
15
16 override fun onCreate() {
17 super.onCreate()
18 DaggerAppComponent.builder()
19 .application(this)
20 .build()
21 .inject(this)
22 }
23 }

AppController.kt hosted with ❤ by GitHub view raw

Note: Don’t forget to add this custom Application class to the AndroidManifest.xml

<application
android:name=".AppController"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"/>

Step 7: Configure MainActivity class


So now we need to create our Activity class.

MainActivity.java
1 public class MainActivity extends AppCompatActivity {
2
3 /*
4 * Step 1: Here as mentioned in Step 5, we need to
5 * inject the ViewModelFactory. The ViewModelFactory class
6 * has a list of ViewModels and will provide
7 * the corresponding ViewModel in this activity
8 * */
9 @Inject
10 ViewModelFactory viewModelFactory;
11
12 /*
13 * I am using DataBinding
14 * */
15 private MainActivityBinding binding;
16
17 /*
18 * This is our ViewModel class
19 * */
20 private MovieListViewModel movieListViewModel;
21
22 private MoviesListAdapter moviesListAdapter;
23
24 @Override
25 protected void onCreate(@Nullable Bundle savedInstanceState) {
26 /*
27 * Step 2: Remember in our ActivityModule, we
28 * defined MainActivity injection? So we need
29 * to call this method in order to inject the
30 * ViewModelFactory into our Activity
31 * */
32 AndroidInjection.inject(this);
33
34 super.onCreate(savedInstanceState);
35 initialiseView();
36 initialiseViewModel();
37 }
38
39 /*
40 * Initialising the View using Data Binding
41 * */
42 private void initialiseView() {
43 binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
44
45 moviesListAdapter = new MoviesListAdapter(this);
46 binding.moviesList.setLayoutManager(new LinearLayoutManager(getApplicationContext(), Linea
47 binding.moviesList.setAdapter(moviesListAdapter);
48
49 /* SnapHelper to change the background of the activity based on the list item
50 * currently visible */
51 SnapHelper startSnapHelper = new PagerSnapHelper(position -> {
52 MovieEntity movie = moviesListAdapter.getItem(position);
53 binding.overlayLayout.updateCurrentBackground(movie.getPosterPath());
54 });
55 startSnapHelper.attachToRecyclerView(binding.moviesList);
56 }
57
58
59 /*
60 * Step 3: Initialising the ViewModel class here.
61 * We are adding the ViewModelFactory class here.
62 * We are observing the LiveData
63 * */
64 private void initialiseViewModel() {
65 movieListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel
66 movieListViewModel.getMoviesLiveData().observe(this, resource -> {
67 if(resource.isLoading()) {
68 displayLoader();
69
70 } else if(!resource.data.isEmpty()) {
71 updateMoviesList(resource.data);
72
73 } else handleErrorResponse();
74 });
75
76 /* Fetch movies list */
77 movieListViewModel.loadMoreMovies();
78 }
79
80 private void displayLoader() {
81 binding.moviesList.setVisibility(View.GONE);
82 binding.loaderLayout.rootView.setVisibility(View.VISIBLE);
83 binding.loaderLayout.loader.start();
84 }
85
86 private void hideLoader() {
87 binding.moviesList.setVisibility(View.VISIBLE);
88 binding.loaderLayout.rootView.setVisibility(View.GONE);
89 binding.loaderLayout.loader.stop();
90 }
91
92 private void updateMoviesList(List<MovieEntity> movies) {
93 hideLoader();
94 binding.emptyLayout.emptyContainer.setVisibility(View.GONE);
95 binding.moviesList.setVisibility(View.VISIBLE);
96 moviesListAdapter.setItems(movies);
97 }
98
99
100 private void handleErrorResponse() {
101 hideLoader();
102 binding.moviesList.setVisibility(View.GONE);
103 binding.emptyLayout.emptyContainer.setVisibility(View.VISIBLE);
104 }
105 }

MainActivity.java hosted with ❤ by GitHub view raw


MainActivity.kt
1 class MainActivity : AppCompatActivity() {
2
3
4 /*
5 * Step 1: Here as mentioned in Step 5, we need to
6 * inject the ViewModelFactory. The ViewModelFactory class
7 * has a list of ViewModels and will provide
8 * the corresponding ViewModel in this activity
9 * */
10 @Inject
11 internal lateinit var viewModelFactory: ViewModelProvider.Factory
12
13
14 /*
15 * I am using DataBinding
16 * */
17 private lateinit var binding: MainActivityBinding
18
19
20 /*
21 * This is our ViewModel class
22 * */
23 lateinit var moviesListViewModel: MovieListViewModel
24
25
26 private lateinit var moviesListAdapter: MoviesListAdapter
27
28 override fun onCreate(savedInstanceState: Bundle?) {
29
30 /*
31 * Step 2: Remember in our ActivityModule, we
32 * defined MainActivity injection? So we need
33 * to call this method in order to inject the
34 * ViewModelFactory into our Activity
35 * */
36 AndroidInjection.inject(this)
37
38 super.onCreate(savedInstanceState)
39 initialiseView()
40 initialiseViewModel()
41 }
42
43
44 /*
45 * Initialising the View using Data Binding
46 * */
47 private fun initialiseView() {
48 binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
49
50 moviesListAdapter = MoviesListAdapter(this)
51 binding.moviesList.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutMan
52 binding.moviesList.adapter = moviesListAdapter
53
54 val startSnapHelper = PagerSnapHelper(
55 object : RecyclerSnapItemListener {
56 override fun onItemSnap(position: Int) {
57 val movie = moviesListAdapter.getItem(position)
58 binding.overlayLayout.updateCurrentBackground(movie.getFormattedPosterPath
59 }
60 }
61 )
62 startSnapHelper.attachToRecyclerView(binding.moviesList)
63 }
64
65
66 /*
67 * Step 3: Initialising the ViewModel class here.
68 * We are adding the ViewModelFactory class here.
69 * We are observing the LiveData
70 * */
71 private fun initialiseViewModel() {
72 moviesListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewMode
73 moviesListViewModel.getMoviesLiveData().observe(this, Observer { resource ->
74 if (resource!!.isLoading) {
75 displayLoader()
76
77 } else if (resource.data != null && !resource.data.isEmpty()) {
78 updateMoviesList(resource.data)
79
80 } else
81 handleErrorResponse()
82 })
83 /* Fetch movies list */
84 moviesListViewModel.loadMoreMovies()
85 }
86
87
88 private fun displayLoader() {
89 binding.moviesList.visibility = View.GONE
90 binding.loaderLayout.rootView.visibility = View.VISIBLE
91 }
92
93 private fun hideLoader() {
94 binding.moviesList.visibility = View.VISIBLE
95 binding.loaderLayout.rootView.visibility = View.GONE
96 }
97
98 private fun updateMoviesList(movies: List<MovieEntity>) {
99 hideLoader()
100 binding.emptyLayout.emptyContainer.visibility = View.GONE
101 binding.moviesList.visibility = View.VISIBLE
102 moviesListAdapter.setItems(movies)
103 }
104
105 private fun handleErrorResponse() {
106 hideLoader()
107 binding.moviesList.visibility = View.GONE
108 binding.emptyLayout.emptyContainer.visibility = View.VISIBLE
109 }
110 }

MainActivity.kt hosted with ❤ by GitHub view raw


Note: I am not including the implementation of the xml classes or the RecyclerView adapter
classes here as it is not part of the scope of this article. You can checkout this Github project
to know the detailed implementation.

And that’s it! We build and run our app and this is the output we should receive.
Note: If you would like to know how to implement the BackgroundSwitcherView i.e. change
the background of the activity when the recyclerView is scrolled, checkout this link.

Bonus:
We saw how to inject the ViewModelFactory into our MainActivity . If you would like to
inject the ViewModelFactory into a Fragment, we need to do the following
modifications:

1. FragmentModule class:
Create a new class called FragmentModule .

FragmentModule.java
1 @Module
2 public abstract class FragmentModule {
3 /*
4 * We define the name of the Fragment we are going
5 * to inject the ViewModelFactory into. i.e. in our case
6 * The name of the fragment: MovieListFragment
7 */
8 @ContributesAndroidInjector
9 abstract MovieListFragment contributeMovieListFragment();
10 }

FragmentModule.java hosted with ❤ by GitHub view raw

FragmentModule.kt

1 @Module
2 abstract class FragmentModule {
3 /*
4 * We define the name of the Fragment we are going
5 * to inject the ViewModelFactory into. i.e. in our case
6 * The name of the fragment: MovieListFragment
7 */
8 @ContributesAndroidInjector
9 abstract fun contributeMovieListFragment(): MovieListFragment
10 }

FragmentModule.kt hosted with ❤ by GitHub view raw

2. Modify ActivityModule class:


Add the newly created FragmentModule to whichever Activity you want to inject. i.e. in
our case MainActivity .
ActivityModule.java

1 @Module
2 public abstract class ActivityModule {
3
4 /*
5 * We modify our ActivityModule by adding the
6 * FragmentModule to the Activity which contains
7 * the fragment
8 */
9 @ContributesAndroidInjector(modules = FragmentModule.class)
10 abstract MainActivity contributeMainActivity();
11 }

ActivityModule.java hosted with ❤ by GitHub view raw

ActivityModule.kt

1 @Module
2 abstract class ActivityModule {
3 /*
4 * We modify our ActivityModule by adding the
5 * FragmentModule to the Activity which contains
6 * the fragment
7 */
8 @ContributesAndroidInjector(modules = [FragmentModule::class])
9 abstract fun contributeMainActivity(): MainActivity
10 }

ActivityModule.kt hosted with ❤ by GitHub view raw


3. Modify MainActivity class:
We need to implement HasSupportFragmentInjector in our Activity if we want to inject
the ViewModelFactory class in our Fragment. And also, move all our
RecyclerView.Adapter and ViewModel implementation to the Fragment class.

MainActivity.java
1 public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
2
3 /*
4 * Step 1: Rather than injecting the ViewModelFactory
5 * in the activity, we are going to implement the
6 * HasActivityInjector and inject the ViewModelFactory
7 * into our MovieListFragment
8 * */
9 @Inject
10 DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
11
12 @Override
13 public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
14 return dispatchingAndroidInjector;
15 }
16
17 /*
18 * I am using DataBinding
19 * */
20 private MainActivityBinding binding;
21
22
23 @Override
24 protected void onCreate(@Nullable Bundle savedInstanceState) {
25 /*
26 * Step 2: We still need to inject this method
27 * into our activity so that our fragment can
28 * inject the ViewModelFactory
29 * */
30 AndroidInjection.inject(this);
31
32 super.onCreate(savedInstanceState);
33 initialiseView();
34 }
35
36 /*
37 * Initialising the View using Data Binding
38 * */
39 private void initialiseView() {
40 binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
41 }
42
43 public void updateBackground(String url) {
44 binding.overlayLayout.updateCurrentBackground(url);
45 }
46 }

MainActivity.java hosted with ❤ by GitHub view raw


MainActivity.kt
1 class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
2
3
4 /*
5 * Step 1: Rather than injecting the ViewModelFactory
6 * in the activity, we are going to implement the
7 * HasActivityInjector and inject the ViewModelFactory
8 * into our MovieListFragment
9 * */
10 @Inject
11 lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
12
13 override fun supportFragmentInjector(): DispatchingAndroidInjector<Fragment> {
14 return dispatchingAndroidInjector
15 }
16
17
18 /*
19 * I am using DataBinding
20 * */
21 private lateinit var binding: MainActivityBinding
22
23
24 override fun onCreate(savedInstanceState: Bundle?) {
25
26 /*
27 * Step 2: We still need to inject this method
28 * into our activity so that our fragment can
29 * inject the ViewModelFactory
30 * */
31 AndroidInjection.inject(this)
32
33 super.onCreate(savedInstanceState)
34 initialiseView()
35 }
36
37
38 /*
39 * Initialising the View using Data Binding
40 * */
41 private fun initialiseView() {
42 binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
43 }
44
45 fun updateBackground(url: String?) {
46 binding.overlayLayout.updateCurrentBackground(url)
47 }
48 }

MainActivity.kt hosted with ❤ by GitHub view raw


4. Add MovieFragment class:
Lastly, we need to create our Fragment class. We inject the ViewModelFactory into the
Fragment and initialise the viewModel. The remaining implementation is the same.

MovieListFragment.java
1 public class MovieListFragment extends Fragment {
2
3 /*
4 * Step 1: Here, we need to inject the ViewModelFactory.
5 * */
6 @Inject
7 ViewModelFactory viewModelFactory;
8
9 /*
10 * I am using DataBinding
11 * */
12 private MovieFragmentBinding binding;
13
14 /*
15 * This is our ViewModel class
16 * */
17 MovieListViewModel movieListViewModel;
18
19 private MoviesListAdapter moviesListAdapter;
20
21 @Override
22 public void onCreate(@Nullable Bundle savedInstanceState) {
23 super.onCreate(savedInstanceState);
24 /*
25 * Step 2: Remember in our FragmentModule, we
26 * defined MovieListFragment injection? So we need
27 * to call this method in order to inject the
28 * ViewModelFactory into our Fragment
29 * */
30 AndroidSupportInjection.inject(this);
31 initialiseViewModel();
32 }
33
34 @Nullable
35 @Override
36 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nu
37 binding = DataBindingUtil.inflate(inflater, R.layout.fragment_movie_list, container, false
38 return binding.getRoot();
39 }
40
41 @Override
42 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
43 super.onViewCreated(view, savedInstanceState);
44 initialiseView();
45 }
46
47 private void initialiseView() {
48 moviesListAdapter = new MoviesListAdapter(getActivity());
49 binding.moviesList.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayout
50 binding.moviesList.setAdapter(moviesListAdapter);
51
52 /* SnapHelper to change the background of the activity based on the list item
53 * currently visible */
54 SnapHelper startSnapHelper = new PagerSnapHelper(position -> {
55 MovieEntity movie = moviesListAdapter.getItem(position);
56 ((MainActivity)getActivity()).updateBackground(movie.getPosterPath());
57 });
58 startSnapHelper.attachToRecyclerView(binding.moviesList);
59 }
60
61
62 private void initialiseViewModel() {
63 movieListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewModel
64 movieListViewModel.getMoviesLiveData().observe(this, resource -> {
65 if(resource.isLoading()) {
66 displayLoader();
67
68 } else if(!resource.data.isEmpty()) {
69 updateMoviesList(resource.data);
70
71 } else handleErrorResponse();
72 });
73
74 /* Fetch movies list */
75 movieListViewModel.loadMoreMovies();
76 }
77
78 private void displayLoader() {
79 binding.moviesList.setVisibility(View.GONE);
80 binding.loaderLayout.rootView.setVisibility(View.VISIBLE);
81 }
82
83 private void hideLoader() {
84 binding.moviesList.setVisibility(View.VISIBLE);
85 binding.loaderLayout.rootView.setVisibility(View.GONE);
86 }
87
88 private void updateMoviesList(List<MovieEntity> movies) {
89 hideLoader();
90 binding.emptyLayout.emptyContainer.setVisibility(View.GONE);
91 binding.moviesList.setVisibility(View.VISIBLE);
92 moviesListAdapter.setItems(movies);
93 }
94
95
96 private void handleErrorResponse() {
97 hideLoader();
98 binding.moviesList.setVisibility(View.GONE);
99 binding.emptyLayout.emptyContainer.setVisibility(View.VISIBLE);
100 }
101 }

MovieListFragment.java hosted with ❤ by GitHub view raw


MovieListFragment.kt
1 class MovieListFragment : Fragment() {
2
3
4 /*
5 * Step 1: Here, we need to inject the ViewModelFactory.
6 * */
7 @Inject
8 internal lateinit var viewModelFactory: ViewModelProvider.Factory
9
10 /*
11 * I am using DataBinding
12 * */
13 private lateinit var binding: MovieFragmentBinding
14
15
16 /*
17 * This is our ViewModel class
18 * */
19 lateinit var moviesListViewModel: MovieListViewModel
20
21
22 private lateinit var moviesListAdapter: MoviesListAdapter
23
24 override fun onCreate(savedInstanceState: Bundle?) {
25 super.onCreate(savedInstanceState)
26
27 /*
28 * Step 2: Remember in our FragmentModule, we
29 * defined MovieListFragment injection? So we need
30 * to call this method in order to inject the
31 * ViewModelFactory into our Fragment
32 * */
33 AndroidSupportInjection.inject(this)
34 initialiseViewModel()
35 }
36
37
38 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState
39 binding = DataBindingUtil.inflate(inflater, R.layout.fragment_movie_list, container, false
40 return binding.root
41 }
42
43 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
44 super.onViewCreated(view, savedInstanceState)
45 initialiseView()
46 }
47
48 private fun initialiseView() {
49 moviesListAdapter = MoviesListAdapter(requireActivity())
50 binding.moviesList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManag
51 binding.moviesList.adapter = moviesListAdapter
52
53 val startSnapHelper = PagerSnapHelper(
54 object : RecyclerSnapItemListener {
55 override fun onItemSnap(position: Int) {
56 val movie = moviesListAdapter.getItem(position)
57 (requireActivity() as MainActivity).updateBackground(movie.getFormattedPo
58 }
59 }
60 )
61 startSnapHelper.attachToRecyclerView(binding.moviesList)
62 }
63
64
65 private fun initialiseViewModel() {
66 moviesListViewModel = ViewModelProviders.of(this, viewModelFactory).get(MovieListViewMode
67 moviesListViewModel.getMoviesLiveData().observe(this, Observer { resource ->
68 if (resource!!.isLoading) {
69 displayLoader()
70
71 } else if (resource.data != null && !resource.data.isEmpty()) {
72 updateMoviesList(resource.data)
73
74 } else
75 handleErrorResponse()
76 })
77 /* Fetch movies list */
78 moviesListViewModel.loadMoreMovies()
79 }
80
81
82 private fun displayLoader() {
83 binding.moviesList.visibility = View.GONE
84 binding.loaderLayout.rootView.visibility = View.VISIBLE
85 }
86
87 private fun hideLoader() {
88 binding.moviesList.visibility = View.VISIBLE
89 binding.loaderLayout.rootView.visibility = View.GONE
90 }
91
92 private fun updateMoviesList(movies: List<MovieEntity>) {
93 hideLoader()
94 binding.emptyLayout.emptyContainer.visibility = View.GONE
95 binding.moviesList.visibility = View.VISIBLE
96 moviesListAdapter.setItems(movies)
97 }
98
99 private fun handleErrorResponse() {
100 hideLoader()
101 binding.moviesList.visibility = View.GONE
102 binding.emptyLayout.emptyContainer.visibility = View.VISIBLE
103 }
104 }

MovieListFragment.kt hosted with ❤ by GitHub view raw


5. Add FragmentModule class to AppComponent:

@Component(
modules = [
ApiModule::class,
DbModule::class,
ViewModelModule::class,
ActivityModule::class,
FragmentModule::class,
AndroidSupportInjectionModule::class]
)

You can checkout this commit to find out the differences between injecting into
Activity and injecting into fragment for Java and this commit for Kotlin.

And that’s it! 😄. I know it feels overwhelming and kinda a lot of work but in the end
it’s worth it, I think.
You can find the GitHub link for this sample implementation here:

The master branch — contains Dagger implementation in Activity in Java.

The kotlin_support branch — contains Dagger implementation in Activity in


Kotlin.

Fragment injection implementation can be found under:

inject_into_fragment_dagger_java — contains Dagger implementation in


Fragment in Java.

inject_into_fragment_kotlin — contains Dagger implementation in Fragment in


Kotlin.

For those of you interested in the whole app implementation, (and not just the
simplified version we implemented here), you can checkout the Github project from
here.

The master branch — code in Java

The kotlin_support branch — code in Kotlin

I hope you enjoyed this article and found it useful, if so please hit the Clap button. Let
me know your thoughts in the comments section.

Happy coding! Thanks for reading!

Android App Development Dagger Android Android AndroidDev

Android Development
Written by Anitaa Murthy Follow

3.4K Followers · Writer for AndroidPub

Android Developer @ Automattic https://github.com/anitaa1990

More from Anitaa Murthy and AndroidPub

Anitaa Murthy in AndroidPub Ashish Rawat in AndroidPub

Android Interview Questions Cheat Wireless Debugging through ADB in


Sheet — Part I Android using WiFi
A set of questions that I have accumulated over the In this article I am going to show you how you can
years in preparation for my many Android… debug your android app over WiFi.

29 min read · May 30, 2018 3 min read · Jan 4, 2019

15.4K 71 949 8
Ankit Sinhal in AndroidPub Anitaa Murthy in AndroidPub

Android Activity Launch Mode 9 ways to avoid memory leaks in


Launch mode is an instruction for Android OS Android
which specifies how the activity should be… I have been an android developer for quite some
time now. And I realised that most of that time, I…

5 min read · Jan 14, 2017 6 min read · May 16, 2018

3.2K 17 3.6K 22

See all from Anitaa Murthy See all from AndroidPub

Recommended from Medium

Ruslan Kim in ProAndroidDev Parita Dey

Get to know AOSP. I had a struggle to Dependency Injection with Hilt in


navigate to a Service. Android
Working with AOSP after years of working on Simplify the Word — Dependency Injection
Android Apps, searching services and aidl impls…
4 min read · Jun 15 7 min read · Oct 15

32 1 10

Lists

Medium Publications Accepting Staff Picks


Story Submissions 514 stories · 468 saves
154 stories · 1110 saves

Duggu Hussain Abbas

Understanding Eventbus with kotlin Building Android Apps with MVVM and
Flow Clean Architecture
The EventBus pattern is a popular design pattern In the world of Android development, designing a
for communication between different parts of an… robust and maintainable architecture is crucial.…

2 min read · Nov 10 6 min read · Jun 8

44 4 70

Karishma Agrawal in Level Up Coding Oğuzhan Aslan

Android Interview questions| PART -4 Dynamic App Icon In Android


To prepare for Interviews, I have designed a series You’ve probably come across those apps that can
of android-related interview questions. You can… pull off a neat trick — changing their app icon,…
· 9 min read · Sep 26 5 min read · Sep 25

78 442 4

See more recommendations

You might also like