Thursday, 12 September 2024

Sealed class | MVVM | Retrofit | RecyclerView | Recycler Adapter | Coroutines Flow | Image Loader in Adapter

 1. RetrofitService

object RetrofitService {
private const val URL = "https://jsonplaceholder.typicode.com/"
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()

fun getRetroService(): ApiService {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(URL)
.build().create(ApiService::class.java)
}
}
2. ApiService
interface ApiService {
@GET("photos")
suspend fun getPhoto():List<PhotoResponseItem>
}
3. ApiStateController
sealed class ApiStateController {
object Loading : ApiStateController()
object Empty : ApiStateController()
data class Success(val data: List<PhotoResponseItem>) : ApiStateController()
data class Failure(val msg: String) : ApiStateController()
}
4. PhotoRepository
class PhotoRepository(private val apiService: ApiService) {

fun getPhoto():Flow<List<PhotoResponseItem>> = flow {
emit(apiService.getPhoto())
}.flowOn(Dispatchers.IO)
}
5.PhotoViewModel
class PhotoViewModel(private val photoRepository: PhotoRepository) : ViewModel() {
private val _photo: MutableStateFlow<ApiStateController> =
MutableStateFlow(ApiStateController.Empty)
val photo: StateFlow<ApiStateController>
get() = _photo

fun getPhoto() {
viewModelScope.launch {
_photo.value = ApiStateController.Loading
photoRepository.getPhoto()
.catch { e ->
_photo.value = ApiStateController.Failure(e.message.toString())
}.collect { data ->
_photo.value = ApiStateController.Success(data)
}
}
}
}
6.PhotoViewModelFactory
class PhotoViewModelFactory(private val photoRepository: PhotoRepository) :
ViewModelProvider.NewInstanceFactory() {

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PhotoViewModel(photoRepository) as T
}
}
7.PhotoActivity
class PhotoActivity : AppCompatActivity() {
private lateinit var photoViewModel: PhotoViewModel
private lateinit var binding: ActivityPhotoBinding
private lateinit var photoAdapter: PhotoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_photo)
initRecyclerView()

val api = RetrofitService.getRetroService()
val repository = PhotoRepository(api)
val factory = PhotoViewModelFactory(repository)
photoViewModel = ViewModelProvider(this, factory)[PhotoViewModel::class.java]
photoViewModel.getPhoto()
getPhoto()
}

private fun getPhoto() {
lifecycle.coroutineScope.launchWhenStarted {
photoViewModel.photo.collect { it ->
when (it) {
ApiStateController.Empty -> {
Log.d("Response====", "===Empty=====")

}

is ApiStateController.Failure -> {
Log.d("Response====", "===Error:= ${it.msg}=====")

binding.progress.visibility = View.GONE
Snackbar.make(binding.root, it.msg, Snackbar.LENGTH_SHORT).show()

}

ApiStateController.Loading -> {
binding.progress.visibility = View.VISIBLE
Log.d("Response====", "===Loading=====")
}

is ApiStateController.Success -> {
binding.progress.visibility = View.GONE
Log.d("Response is===", "${it.data}")
photoAdapter.setData(it.data)
photoAdapter.notifyDataSetChanged()

}
}
}
}
}

private fun initRecyclerView() {
photoAdapter = PhotoAdapter(ArrayList())
binding.recyclerView.apply {
setHasFixedSize(true)
adapter = photoAdapter
layoutManager = LinearLayoutManager(this@PhotoActivity)
}
}

}
8. PhotoAdapter
class PhotoAdapter(private var photoList: List<PhotoResponseItem>) :
RecyclerView.Adapter<PhotoAdapter.PhotoViewHolder>() {
lateinit var binding: EachRowBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder {
binding = EachRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PhotoViewHolder(binding.root)
}

override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
binding.id.text = photoList[position].id.toString()
binding.title.text = photoList[position].title
Glide
.with(binding.thumbnailUrl)
.load(photoList[position].thumbnailUrl)
.centerCrop()
.into(binding.thumbnailUrl);

}

override fun getItemCount(): Int {
return photoList.size
}

class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}

fun setData(photoData: List<PhotoResponseItem>) {
this.photoList = photoData
notifyDataSetChanged()
}


}
9. photo_activity
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>

</data>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="30dp"
android:visibility="gone" />

</RelativeLayout>
</layout>

10. each_row.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>

</data>

<androidx.cardview.widget.CardView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="8dp"
android:orientation="vertical"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/thumbnailUrl"
android:layout_width="100dp"
android:layout_height="100dp" />

<LinearLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/thumbnailUrl"
android:orientation="vertical">

<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="Hello world" />

<TextView
android:id="@+id/id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
tools:text="1" />

</LinearLayout>

</RelativeLayout>


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

Wednesday, 11 September 2024

Simple Example of Sealed class using ViewModel

 1.  MyViewModel

MyViewModel

class MyViewModel : ViewModel() {
private val _state = MutableStateFlow<StateController>(StateController.Empty)
val state: StateFlow<StateController>
get() = _state

fun login(name: String, pass: String) {
_state.value = StateController.Loading
viewModelScope.launch {
delay(2000)

if (name == "simi" && pass == "123") {
_state.value = StateController.Success("Authenticated")
} else {
_state.value = StateController.Error("Invalid UserID & Password")
}
}
}
}
2. StateController
StateController
package com.example.sealedviewmodelex

sealed class StateController {
data object Loading : StateController()
data object Empty : StateController()
data class Success(val data: String) : StateController()
data class Error(val msg: String) : StateController()

}
3. MainActivity
MAinActivity
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MyViewModel
lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
binding.clickMe.setOnClickListener {
viewModel.login(binding.username.text
.toString(), binding.password.text.toString())
}
lifecycle.coroutineScope.launchWhenCreated {
viewModel.state.collect {
when (it) {
is StateController.Error -> {
binding.progress.visibility=View.GONE
Snackbar.make(binding.root, it.msg, Snackbar.LENGTH_SHORT).show()
}

is StateController.Loading -> {
binding.progress.visibility = View.VISIBLE
}

is StateController.Success -> {
binding.progress.visibility=View.GONE
Snackbar.make(binding.root, "Authenticated", Snackbar.LENGTH_SHORT).show()
}

StateController.Empty -> {

}
}
}
}
}
}

4. Dependencies
libs class:
lifecycle_version = "2.8.4"
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", 
name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle_version" }
buildFeatures{
dataBinding=true
}
implementation(libs.androidx.lifecycle.viewmodel.ktx)