Thursday, 29 August 2024

View Image url from api to ImageView in RecyclerView Adapter class Using DataBinding

 1. build.gradle.kts

dependencies{
//image  loader
implementation(libs.androidx.glide.bumptech)
}
plugins {
id("org.jetbrains.kotlin.kapt")
}
2. libs.version.toml
androidx-glide-bumptech={group="com.github.bumptech.glide",name="glide",version="4.16.0"}
3.Create a databinding utils class like below:
package com.example.mvvmtest3

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import java.net.URL

@BindingAdapter("android:bindImage")
fun loadImage(view: ImageView, url: String) {
Glide.with(view)
.load(url)
.into(view)
}
4. recycler_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>

<variable
name="album"
type="com.example.mvvmtest3.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_height="wrap_content"
android:layout_margin="10dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
    android:layout_width="100dp"
android:bindImage="@{album.title}"
android:id="@+id/imageView"
android:contentDescription="@string/app_name"
android:layout_height="100dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_toRightOf="@+id/imageView"
android:orientation="vertical">

<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@{album.title}"
android:textColor="@color/black"
android:textStyle="bold" />

<TextView
android:id="@+id/id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(album.id)}" />

<TextView
android:id="@+id/userId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@{String.valueOf(album.userId)}" />

</LinearLayout>

</RelativeLayout>
</androidx.cardview.widget.CardView>

</LinearLayout>
</layout>

View Image url from api to ImageView in RecyclerView Adapter class

1. PhotoAdapter.kt

override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
val photoList = photoResponceItem[position]
holder.title.text = photoList.title
holder.id.text = photoList.id.toString()
Glide
.with(holder.imageView)
.load(photoList.thumbnailUrl)
.centerCrop()
.into(holder.imageView);
}
inner class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val imageView: ImageView =itemView.findViewById(R.id.imageView)
}
2. build.gradle.kts
dependencies{
//image  loader
implementation(libs.androidx.glide.bumptech)
}
plugins {
id("org.jetbrains.kotlin.kapt")
}
3. libs.version.toml
androidx-glide-bumptech={group="com.github.bumptech.glide",name="glide",version="4.16.0"}

Monday, 26 August 2024

Kotlin | MVVM | API call Retrofit | viewModel | repository class |RecyclerView example

 

1. First add dependency in  the app  gradle file : build.gradle.kts(:app)

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>