Kotlin Recycler view using Dagger With Hilt

Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project. 

Doing manual dependency injection requires you to construct every class and its dependencies by hand, and to use containers to reuse and manage dependencies.

  • Kotlin
  • Dagger With Hilt 
  • Retrofit
  • Recycler view
  • Live Data
  • View model
  • View Binding

Basically, to understand Dagger we have to understand the 4 major annotations,
  • Module
  • Component
  • Provides
  • Inject
Now, a module is a class and we annotate it with @Module for Dagger to understand it as a Module.

A component is an interface, which is annotated with @Component and takes modules in it. (But now, this annotation is not required in Dagger-Hilt)

Provides are annotation which is used in Module class to provide dependency and,

Inject is an annotation that is used to define a dependency inside the consumer.

Overview:

Step 1: Add hilt in project Gradle 
Step 2: Add some dependencies in the app Gradle ex. hilt, ViewModel, coroutines, retrofit,  live data, view binding
Step 3: Add Internet permission, clear traffic, and add a class name in the Manifest file
Step 4:  Create an application class by annotating @HiltAndroidApp:
Step 5:  Create dependency Injection for Network Module which will provide APIService class 
Step 6:  Create a Model  for retrofit:
Step 7:  Create a retrofit Service interface.
Step 8: Now create an implementing class for the Retrofit service class:
Step 9: Create here for User Repository : 
Step 10: Now Create Activity for view with annotating @AndroidEntryPoint:
Step 11: Create an Adapter 
Step 13: Create ViewModel to attache with a view: with annotating @HiltViewModel
Step 14: Create activity XML in the layout folder 
Step 15: Here have one layout for the Adapter Layout layout_user.xml 

Setting up Dagger-Hilt

Step 1: Add hilt in project Gradle :
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath("com.google.dagger:hilt-android-gradle-plugin:2.40.5")
}
}

// Top-level build
file where you can add configuration options common to all sub-projects/modules.
plugins {
id
'com.android.application' version '7.1.3' apply false
id
'com.android.library' version '7.1.3' apply false
id
'org.jetbrains.kotlin.android' version '1.6.21' apply false
}

task clean(
type: Delete) {
delete rootProject.buildDir
}
Step 2: Add some dependencies in the app Gradle ex. hilt, ViewModel, coroutines, retrofit,  live data, view binding: 
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'

id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'

}

android {
compileSdk 32

defaultConfig {
applicationId "com.hilt.hiltexample"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'

// hilt android
implementation 'com.google.dagger:hilt-android:2.41'
kapt 'com.google.dagger:hilt-android-compiler:2.41'

// kotlin bg running
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'

// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.6'
implementation 'com.google.code.gson:gson:2.9.0'

// view model dependency
implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'

// LiveData
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'


testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Step 3: Add Internet permission, clear traffic, and add a class name in the Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hilt.hiltexample">

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

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

<activity
android:name=".views.MainActivity"
android:exported="true">

<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Now start Hilt Dagger annotation from the application class: 
Step 4:  Create an application class with annotate @HiltAndroidApp:

package com.hilt.hiltexample

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class HiltApplication : Application()

Step 5:  Create dependency Injection for Network Module which will provide APIService class : 
* di / NetworkModule.kt
package com.hilt.hiltexample.di

import com.hilt.hiltexample.network.APIServices
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory


@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {

@Provides
fun provideBaseUrl(): String = "https://datanapps.com/DNARestAPIs/"


@Provides
fun provideRetrofit(baseUrl:String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

@Provides
fun provideApiService(retrofit: Retrofit) : APIServices = retrofit.create(APIServices::class.java)

}
..
Now start with Retrofit implementation :
Step 6:  Create a Model  for retrofit:
* models -> BaseUser.kt

package com.hilt.hiltexample.models.base

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import com.hilt.hiltexample.models.User

class BaseUser {
@SerializedName("userlist")
@Expose
var userlist: List<User>? = null

@SerializedName("totalrecords")
@Expose
var totalrecords: Int? = null

@SerializedName("message")
@Expose
var message: String? = null
}

* models -> User.kt

package com.hilt.hiltexample.models

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

class User {
@SerializedName("id")
@Expose
var id: String?= null

@SerializedName("FirstName")
@Expose
var firstName: String?= null
@SerializedName("LastName")
@Expose
var lastName: String?= null
@SerializedName("Email")
@Expose
var email: String?= null
@SerializedName("ContactNo")
@Expose
var contactNo: String?= null
@SerializedName("DOB")
@Expose
var dOB: String?= null
@SerializedName("Image")
@Expose
var image: String?= null
}
* Sample GET API response:
{
"userlist": [
{
"id": "1",
"FirstName": "Renold",
"LastName": "Bolt",
"Email": "renold.bold@gmail.com",
"ContactNo": "+65-12345678",
"DOB": "2019-06-23 18:22:45",
"Image": "https:\/\/datanapps.com\/public\/dnarestapi\/user\/renold1.png"
},
{
"id": "2",
"FirstName": "Data N",
"LastName": "apps",
"Email": "datanapps@gmail.com",
"ContactNo": "+68-12345678",
"DOB": "2019-06-27 08:56:18",
"Image": "https:\/\/datanapps.com\/public\/dnarestapi\/user\/datan10.png"
}
],
"totalrecords": 11,
"message": "success"
}
..
..
Step 7:  Create a retrofit Service interface.
* network: APIServicesImpl.kt
package com.hilt.hiltexample.network

import com.hilt.hiltexample.models.base.BaseUser
import retrofit2.http.GET

interface APIServices {

@GET("getUserLists")
suspend fun getUser() : BaseUser
}
..
Step 8: Now create implement class for the Retrofit service class:
* network: APIServices.kt
package com.hilt.hiltexample.network

import com.hilt.hiltexample.models.base.BaseUser
import javax.inject.Inject

class APIServicesImpl
@Inject constructor(private val apiServices: APIServices) {

suspend fun getUserList(): BaseUser = apiServices.getUser()
}
..
Step 9: Create here for User Repository : 
* repository : UserRepositoryImpl.kt
package com.hilt.hiltexample.repository

import com.hilt.hiltexample.models.base.BaseUser
import com.hilt.hiltexample.network.APIServicesImpl
import kotlinx.coroutines.Dispatchers

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject

class UserRepositoryImpl
@Inject constructor(private var apiServicesImpl: APIServicesImpl){

fun getUserList(): Flow<BaseUser> = flow{
val response = apiServicesImpl.getUserList()
emit(response)
}.flowOn(Dispatchers.IO)
}
..
Create a view now.
Step 10: Now Create Activity for view with annotating @AndroidEntryPoint:
* views : MainActivity.kt
package com.hilt.hiltexample.views

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import com.hilt.hiltexample.databinding.ActivityMainBinding
import com.hilt.hiltexample.models.User
import com.hilt.hiltexample.viewmodels.MainViewModels
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private val _mainViewModel: MainViewModels by viewModels()
private lateinit var _binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(_binding.root)

_mainViewModel.userResponse.observe(this) {
Log.d("tag01", "----------- :: " + it.userlist!![0].firstName)
setAdapterInRecycleView(it.userlist!!)
}
}

private fun setAdapterInRecycleView(userList: List<User>){
_binding.rvUserList.adapter = UserAdapter(userList)
}
}
..
Step 11: Create Adapter :
* views : UserAdapter.kt
package com.hilt.hiltexample.views

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hilt.hiltexample.databinding.LayoutUserBinding
import com.hilt.hiltexample.models.User

class UserAdapter(private val list: List<User>) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
private lateinit var _context : Context
inner class UserViewHolder(private val binding: LayoutUserBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(favoriteMovie: User) {
with(binding) {
tvName.text=(favoriteMovie.firstName)
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val binding = LayoutUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
_context = parent.context
return UserViewHolder(binding)
}

override fun getItemCount(): Int = list.size

override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(list[position])
}
}
..
..
Step 12: Create ViewModel to attache with a view:
* viewmodels : MainViewModels.kt

package com.hilt.hiltexample.viewmodels

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import com.hilt.hiltexample.models.base.BaseUser
import com.hilt.hiltexample.repository.UserRepositoryImpl
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.catch
import javax.inject.Inject


@HiltViewModel
class MainViewModels

@Inject constructor(userRepositoryImpl: UserRepositoryImpl):ViewModel(){

var userResponse : LiveData<BaseUser> = userRepositoryImpl.getUserList()
.catch { exception-> Log.d("asd", "Exception ${exception.message}") }
.asLiveData()
}

..
Step 13: Create activity XML in the layout folder :
* activity_main.xml (with recycler view widget)

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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"
tools:context=".views.MainActivity">


<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvUserList"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/layout_user"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
..
Step 14: Here have one layout for Adapter Layout layout_user.xml :
* layout_user.xml 

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
tools:context=".views.MainActivity">


<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
..


Hilt and Dagger annotations cheat sheet 

This cheat sheet allows you to quickly see what the different Hilt and Dagger annotations do and how to use them



Comments