android {
dataBinding {
enable = true
}
buildFeatures {
viewBinding = true
}
}
dependencies {
//navigation fragment
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
//coroutines
implementation(libs.androidx.coroutines.core.ktx)
implementation(libs.androidx.coroutines.ktx)
// Retrofit
implementation(libs.androidx.retrofit2.converter.gson)
implementation(libs.androidx.retrofit2)
implementation(libs.androidx.okhttp3.logging.interceptor)
//view model
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
//New Material Design
implementation(libs.androidx.android.material.ktx)
implementation(libs.androidx.arch.lifecycle.viewmodel)
// ViewModel and LiveData
implementation(libs.androidx.lifecycle)
}
2. libs.versions.toml
coroutinesVersion = "1.7.1"
retrofit = "2.9.0"
gson = "2.6.2"
okhttp3 = "4.5.0"
lifecycle_version = "2.8.4"
material_version = "1.12.0"
legacySupportV4 = "1.0.0"
fragmentKtx = "1.8.2"
navigationFragmentKtx = "2.7.7"
navigationUiKtx = "2.7.7"
androidx-coroutines-core-ktx = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutinesVersion" }
androidx-coroutines-ktx = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutinesVersion" }
androidx-retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
androidx-retrofit2-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "gson" }
androidx-okhttp3-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle_version" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle_version" }
androidx-android-material-ktx={group="com.google.android.material",name="material",version.ref="material_version"}
androidx-arch-lifecycle-viewmodel={group="android.arch.lifecycle",name="viewmodel",version="1.1.1"}
androidx-lifecycle={group="androidx.lifecycle",name="lifecycle-extensions",version="2.2.0"}
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
3. gradle.properties
android.databinding.enableV2=true
4. MyApi.kt interface
interface MyApi {
@GET("albums")
suspend fun getAlbum(): Response<List<AlbumResponseItem>>
companion object {
operator fun invoke(): MyApi {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://jsonplaceholder.typicode.com/")
.build()
.create(MyApi::class.java)
}
}
}
5. SafeApiRequest.kt abstract class
abstract class SafeApiRequest {
//generic function T of type Any :: apiRequest -is function name
suspend fun <T : Any> apiRequest(apiCall: suspend () -> Response<T>): T {
val response = apiCall.invoke()
if (response.isSuccessful) {
return response.body()!!
} else {
response.code()
throw apiException(response.code().toString())
}
}
}
class apiException(msg: String) : IOException(msg)
6. AlbumResponseItem.kt data class
data class AlbumResponseItem(
val id: Int,
val title: String,
val userId: Int
)
7. AlbumRepository.kt class
class AlbumRepository(private val api: MyApi):SafeApiRequest() {
suspend fun getAlbumRepo()= apiRequest { api.getAlbum() }
}
8. AlbumViewModel.kt class
class AlbumViewModel(private val albumRepository: AlbumRepository) : ViewModel() {
private lateinit var job: Job
private val albumMutableLiveData = MutableLiveData<List<AlbumResponseItem>>()
val albumLiveData: LiveData<List<AlbumResponseItem>>
get() = albumMutableLiveData
fun getAlbumViewModel() {
job = Coroutines.ioMainThreadCall({ albumRepository.getAlbumRepo() },
{ albumMutableLiveData.value = it })
}
override fun onCleared() {
super.onCleared()
if (::job.isInitialized)
job.cancel()
}
}
9. AlbumViewModelFactory.kt class
@Suppress("UNCHECKED_CAST")
class AlbumViewModelFactory(private val albumRepository: AlbumRepository) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AlbumViewModel(albumRepository) as T
}
}
10. AlbumAdapter.kt class
package com.example.mvvmretrofit.utils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.mvvmretrofit.R
import com.example.mvvmretrofit.databinding.ItemListBinding
import com.example.mvvmretrofit.model.AlbumResponseItem
class AlbumAdaper(private val albumResponseItem: List<AlbumResponseItem>) :
RecyclerView.Adapter<AlbumAdaper.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyViewHolder(
DataBindingUtil.inflate<ItemListBinding>(
LayoutInflater.from(parent.context),
R.layout.item_list,
parent,
false
)
)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemListBinding.albumResItem=albumResponseItem[position]
}
override fun getItemCount(): Int {
return albumResponseItem.size
}
inner class MyViewHolder(val itemListBinding: ItemListBinding) :
RecyclerView.ViewHolder(itemListBinding.root)
}
11. Coroutines.kt object class
object Coroutines {
/*Generic function*/
fun <T : Any> ioMainThreadCall(work: suspend () -> T, callback: ((T?) -> Unit)) =
CoroutineScope(Dispatchers.Main).launch {
val data = CoroutineScope(Dispatchers.IO).async {
return@async work()
}.await()
callback(data)
}
}
12. AlbumFragment.kt interface
class AlbumFragment : Fragment() {
lateinit var viewModel: AlbumViewModel
lateinit var factory: AlbumViewModelFactory
lateinit var binding: FragmentAlbumBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAlbumBinding.inflate(layoutInflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val myApi = MyApi()
val repository = AlbumRepository(myApi)
factory = AlbumViewModelFactory(repository)
//viewModel = ViewModelProviders.of(this, factory).get(AlbumViewModel::class.java)
viewModel = ViewModelProvider(this, factory)[AlbumViewModel::class.java]
viewModel.getAlbumViewModel()
viewModel.albumLiveData.observe(viewLifecycleOwner, Observer {albumList->
binding.albumRecyclerView.also {
it.layoutManager = LinearLayoutManager(requireContext())
it.setHasFixedSize(true)
it.adapter = AlbumAdaper(albumList)
}
})
}
}
13. MainActivity.kt class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
14. activity_main.xml
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView2"
android:name="com.example.mvvmretrofit.view.AlbumFragment"
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>
15. fragment_album.kt
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.AlbumFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/albumRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
16. item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="albumResItem"
type="com.example.mvvmretrofit.model.AlbumResponseItem" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_margin="10dp"
app:cardCornerRadius="5dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(albumResItem.id)}" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{albumResItem.title}" />
<TextView
android:id="@+id/userId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(albumResItem.userId)}" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</layout>