Kotlin-Mvvm-DaggerHilt-Retrofit(@Get)-Flow-RecyclerView Example
Step 1:
Open build.gradle(:Project) and add following dependency and rebuild the project.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Step 2:
Open build.gradle(:app) and add following dependency and rebuild the project.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 31
defaultConfig {
applicationId "in.kotlinkatta.kotlinkattademo"
minSdk 21
targetSdk 31
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{
dataBinding true;
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
//room
implementation "androidx.room:room-ktx:2.4.0"
implementation "androidx.room:room-runtime:2.4.0"
kapt "androidx.room:room-compiler:2.4.0"
//multidex
implementation 'androidx.multidex:multidex:2.0.1'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
// view model ktx
implementation 'androidx.activity:activity-ktx:1.4.0'
//hilt viewmodel
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0")
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//moshi
implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
//SharedPreferences
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
//DaggerHilt
implementation 'com.google.dagger:hilt-android:2.40.5'
kapt 'com.google.dagger:hilt-compiler:2.40.5'
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
Step 3:
Open gradle.properties and add following code and rebuild the project.
kapt.use.worker.api=false
Step 4:
Open AndroidManifest.xml and add following code.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.kotlinkatta.kotlinkattademo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".container.BaseApp"
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/Theme.KotlinKattaDemo">
<activity
android:name=".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>
Step 5:
Create new class BaseApp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.container
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class BaseApp : Application() {}
Step 6:
Create new class PostsDataModel.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.model
import androidx.annotation.Keep
@Keep
data class PostsDataModel(
val userId: Int,
val id: Int,
val title: String,
val body: String
){}
Step 7:
Create new interface ApiServices.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network
import `in`.kotlinkatta.kotlinkattademo.model.PostsDataModel
import retrofit2.http.GET
interface ApiServices {
companion object {
const val BASE_URL = "https://jsonplaceholder.typicode.com"
}
@GET("/posts")
suspend fun getPostsDetails(): List<PostsDataModel>
}
Step 8:
Create new class ApiServicesImp.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network
import `in`.kotlinkatta.kotlinkattademo.model.PostsDataModel
import javax.inject.Inject
class ApiServicesImp @Inject constructor(private val apiServices: ApiServices) {
suspend fun getPostsDetails(): List<PostsDataModel> = apiServices.getPostsDetails()
}
Step 9:
Create new object AppModule.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.di
import `in`.kotlinkatta.kotlinkattademo.network.ApiServices
import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import `in`.kotlinkatta.kotlinkattademo.repository.PostsDataRepository
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun providesMoshi(): Moshi = Moshi
.Builder()
.add(KotlinJsonAdapterFactory())
.build()
@Provides
@Singleton
fun providesApiService(moshi: Moshi): ApiServices = Retrofit
.Builder()
.run {
baseUrl(ApiServices.BASE_URL)
addConverterFactory(MoshiConverterFactory.create(moshi).asLenient())
.build()
}.create(ApiServices::class.java)
@Provides
fun providesPostsDataRepository(apiServicesImp: ApiServicesImp): PostsDataRepository =
PostsDataRepository(apiServicesImp)
}
Step 10:
Create new class PostsDataRepository.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.repository
import `in`.kotlinkatta.kotlinkattademo.model.PostsDataModel
import `in`.kotlinkatta.kotlinkattademo.network.ApiServicesImp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
class PostsDataRepository @Inject constructor(
private val apiServicesImp: ApiServicesImp
) {
fun postsDetails(): Flow<List<PostsDataModel>> = flow {
emit(apiServicesImp.getPostsDetails())
}.flowOn(Dispatchers.IO)
}
Step 11:
Create new class NetworkHelper.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NetworkHelper @Inject constructor(@ApplicationContext private val context: Context) {
fun isNetworkConnected(): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val activeNetwork =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
}
Step 12:
Create new class ApiStatePostsData.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.apistate
import `in`.kotlinkatta.kotlinkattademo.model.PostsDataModel
sealed class ApiStatePostsData{
class Success(val data: List<PostsDataModel>) : ApiStatePostsData()
class Failure(val errormsg:Throwable) : ApiStatePostsData()
object Loading : ApiStatePostsData()
object Empty : ApiStatePostsData()
class Message(val msg: String) : ApiStatePostsData() }
Step 13:
Create new class PostsDataViewModel.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.uiviewmodel
import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStatePostsData
import `in`.kotlinkatta.kotlinkattademo.network.NetworkHelper
import `in`.kotlinkatta.kotlinkattademo.repository.PostsDataRepository
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class PostsDataViewModel
@Inject
constructor(
private val postDataRepository: PostsDataRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {
private val _postDataApiState: MutableStateFlow<ApiStatePostsData> = MutableStateFlow(
ApiStatePostsData.Empty
)
val postDataApiState: StateFlow<ApiStatePostsData> = _postDataApiState
fun postLoginDetails() = viewModelScope.launch {
if (networkHelper.isNetworkConnected()) {
postDataRepository.postsDetails()
.onStart {
_postDataApiState.value = ApiStatePostsData.Loading
}.catch { e ->
_postDataApiState.value = ApiStatePostsData.Failure(e)
}.collect { data ->
_postDataApiState.value = ApiStatePostsData.Success(data)
}
}else{
_postDataApiState.value = ApiStatePostsData.Message("No internet connection")
}
}
}
Step 14:
Create new class postsdatarow.xml and add following code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="5dp"
app:cardUseCompatPadding="true"
app:cardElevation="8dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:padding="5dp"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:id="@+id/txttitle"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="title"
android:textSize="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginTop="5dp"
android:id="@+id/txtbody"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="body"
android:textSize="13sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
Step 15:
Create new class PostsDataAdapter.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo.adapter
import `in`.kotlinkatta.kotlinkattademo.databinding.PostsdatarowBinding
import `in`.kotlinkatta.kotlinkattademo.model.PostsDataModel
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import javax.inject.Inject
public class PostsDataAdapter @Inject constructor() :
ListAdapter<PostsDataModel, PostsDataAdapter.PostViewHolder>(
Diff
) {
inner class PostViewHolder(val binding: PostsdatarowBinding) :
RecyclerView.ViewHolder(binding.root)
object Diff : DiffUtil.ItemCallback<PostsDataModel>() {
override fun areItemsTheSame(oldItem: PostsDataModel, newItem: PostsDataModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: PostsDataModel, newItem: PostsDataModel): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
return PostViewHolder(
PostsdatarowBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val postDataModel = getItem(position)
if (postDataModel != null) {
holder.binding.apply {
txttitle.text = "Title : " + postDataModel.title
txtbody.text = postDataModel.body
}
}
}
}
Step 16:
Open layout file activity_main.xml and add following code.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/progressbarProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 17:
Open Activity file MainActivity.kt and add following code.
package `in`.kotlinkatta.kotlinkattademo
import `in`.kotlinkatta.kotlinkattademo.adapter.PostsDataAdapter
import `in`.kotlinkatta.kotlinkattademo.apistate.ApiStatePostsData
import `in`.kotlinkatta.kotlinkattademo.databinding.ActivityMainBinding
import `in`.kotlinkatta.kotlinkattademo.uiviewmodel.PostsDataViewModel
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import javax.inject.Inject
@AndroidEntryPoint
public class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val postsDataViewModel: PostsDataViewModel by viewModels()
@Inject
lateinit var postsDataAdapter: PostsDataAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.progressbarProgress.isVisible = false
initRecyclerView()
getPostData()
}
private fun initRecyclerView() {
binding.apply {
recyclerview.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = postsDataAdapter
}
}
}
private fun getPostData() {
binding.apply {
progressbarProgress.isVisible = true
lifecycleScope.launchWhenStarted {
postsDataViewModel.postLoginDetails()
postsDataViewModel.postDataApiState.collect {
when (it) {
is ApiStatePostsData.Success -> {
Log.d("main", "${it.data}")
progressbarProgress.isVisible = false
recyclerview.isVisible = true
postsDataAdapter.submitList(it.data)
}
is ApiStatePostsData.Failure -> {
progressbarProgress.isVisible = false
Toast.makeText(this@MainActivity, it.errormsg.toString(), Toast.LENGTH_SHORT).show()
}
is ApiStatePostsData.Loading -> {
progressbarProgress.isVisible = true
}
is ApiStatePostsData.Empty -> {
progressbarProgress.isVisible = false
}
is ApiStatePostsData.Message -> {
progressbarProgress.isVisible = false
Toast.makeText(this@MainActivity, it.msg, Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
Comments
Post a Comment