ListAdapter vs. RecyclerView.Adapter — Hangisini, Ne Zaman Kullanmak Gerekir?
ListAdapter aslında RecyclerView.Adapter’ün bir eklentisidir. Temel olarak her ikisinin de çalışma prensipleri aynıdır ve DiffUtil ile listeler arası farkları hesaplayabiliriz.
Gelin öncelikle bu iki yapı arasındaki farkları başlıklar halinde inceleyelim daha sonra kodlara geçelim;
Listeler arası fark hesaplamaları
- RecyclerView.Adapter’e iletilen iki liste arasındaki farkları DiffUtil ile hesaplamamız adaptörümüzü bu değişiklikler hakkında bilgilendirmemiz gerekir. Fakat bu işlem opsiyoneldir.
- ListAdapter ise bu işlemi DiffUtil’e bir nevi yardım eden AsyncListDiffer sınıfı sayesinde arka planda (background thread) otomatik olarak hesaplar. DiffUtill’i yapı (constructor) içinde belirtmek mecburidir.
Yeni liste gönderimi
- Yeni listeyi RecyclerView.Adapter’e gönderirken ilgili fonksiyonu kendimiz yazmamız gerekir.
- Bu işlem için ListAdapter’ün “submitList” isimli kendine ait bir methodu vardır.
Statik/dinamik içerikler
- Eğer listeniz bir kere oluşacak ve değişmeyecekse yani statik bir liste ise RecyclerView.Adapter’ü DiffUtil’siz kullanmanız sizin için daha faydalı olacaktır.
- Fakat eğer WebSocket gibi sürekli akan bir veriyi ya da bir herhangi sebeple içeriği değişecek bir listeyi performanslı şekilde göstermek isterseniz en iyi seçenek ListAdapter olacaktır.
UI değişimleri
Ekran görüntülerinde görebileceğiniz gibi;
- DiffUtil kullanılmış RecylerView.Adapter ve ListAdapter’de ekranda ki değişimler animasyonlu gibi tatlı bir efekt ile gerçekleşirken;
- DiffUtil kullanılmamış RecylerView.Adapter’de liste sanki tamamen yeniden oluşuyor gibi bir görüntü söz konusudur.
Şimdi biraz kod görelim
Öncelikle UI kısmını hazırlayalım;
1 — Fragment ya da Activity’de gösterilecek ana layout;
<?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"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcyView"
android:layout_width="match_parent"
android:layout_height="250dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRefreshList"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="32sp"
android:text="Refresh"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rcyView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2 — RecylerView içerisinde gösterilecek her bir elemanın layout’u;
<?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"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/txtName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name" />
<TextView
android:id="@+id/txtAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/txtName"
app:layout_constraintTop_toTopOf="@+id/txtName"
tools:text="31" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3 — Listelenecek elemanları soyutlamak için bir model oluşturalım;
data class CustomModel(val name: String, val age: Int)
4 — ViewHolder oluşturalım, bu sınıf her iki adaptör içinde kullanılabileceği için ayrı bir dosya olarak ekledim;
class CustomViewHolder(private val binding: CustomLayoutItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: CustomModel) {
binding.txtName.text = item.name
binding.txtAge.text = item.age.toString()
}
}
5— Önce ListAdapter ile başlayalım;
// ListAdapter'ü oluştururken modelimizi ve viewHolder'u jenerik yapı içerisinde,
// diffUtil'i de klasik yapı içerisinde belirtiyoruz.
class CustomListAdapter : ListAdapter<CustomModel, CustomViewHolder>(CustomDiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(createBinding(parent))
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
object CustomDiffCallback : DiffUtil.ItemCallback<CustomModel>() {
override fun areItemsTheSame(oldItem: CustomModel, newItem: CustomModel): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: CustomModel, newItem: CustomModel): Boolean {
return oldItem == newItem
}
}
private fun createBinding(parent: ViewGroup) = CustomLayoutItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
6 — Şimdi de RecyclerView.Adapter için DiffUtil sınıfımızı hazırlayalım;
class CustomDiffUtil(
private val oldList: List<CustomModel>,
private val newList: List<CustomModel>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return when {
oldList[oldItemPosition].name != newList[newItemPosition].name -> false
oldList[oldItemPosition].age != newList[newItemPosition].age -> false
else -> true
}
}
}
7 — RecyclerView.Adapter’ümüzü oluşturalım;
class CustomRecyclerViewAdapter : RecyclerView.Adapter<CustomViewHolder>() {
private var list = emptyList<CustomModel>()
override fun getItemCount(): Int = list.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(createBinding(parent))
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(list[position])
}
// ListAdapter gibi davranması için DiffUtil'i devreye sokuyoruz
fun submitListWithDiffUtil(newList: List<CustomModel>) {
val diffUtil = CustomDiffUtil(list, newList)
val diffResults = DiffUtil.calculateDiff(diffUtil)
list = newList
diffResults.dispatchUpdatesTo(this)
}
// DiffUtil kullanmadığımız zaman adaptörümüze listenin değiştiğini
// bildirmek için notifyDataSetChanged() gibi bir method kullanmamız
// gerekiyor.
@SuppressLint("NotifyDataSetChanged")
fun submitList(newList: List<CustomModel>){
list = newList
notifyDataSetChanged()
}
}
private fun createBinding(parent: ViewGroup) = CustomLayoutItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
Umarım bu makale sizin için faydalı olur. Eksik veya yanlış gördüğünüz, eklemek istediğiniz bir şey olursa asla çekinmeyin.
Bir sonraki yazı büyük ihtimal birden fazla tipteki component’leri tek adaptör ile nasıl yönetiriz hakkında olacak.
Takipte kalın 🙄