RecyclerView Challenge: RecyclerView is Recycleable
How to fix item not consistent in RecyclerView
RecyclerView
Seperti yang kita tahu bahwa RecyclerView merupakan salah satu view yang sering kita pakai untuk menampilkan data dalam bentuk list baik itu vertical maupun horizontal. Berdasarkan situsnya menyebutkan bahwa RecyclerView memiliki kemampuan untuk menampilkan data yang banyak dan lebih gampang penggunaan-nya daripada ListView.
What’s The Problem
Berikut adalah salah satu problem dari penggunaan RecyclerView jika kita tidak paham bagaimana cara kerja dari si RecyclerView.
Jika kita perhatikan dari problem diatas adalah awalnya kita cuma mengisi absensi 5 siswa pertama lalu, kita scroll kebawah dan terus kebawah tapi, kok tiba-tiba saja ada data siswa yang sebelumnya tidak kita isi kok jadi ikut terisi pula sesuai dengan 5 data siswa diawal tadi. Umumnya problem diatas sering kita alami jika kita tidak paham bagaimana sebenarnya cara kerja dari si RecyclerView tersebut.
“Lalu apakah problem diatas merupakan salah satu kelemahan dari si RecyclerView atau memang coding kita yang error?” Jawabannya ialah justru problem diatas adalah salah satu keistimewaan dari si RecyclerView.
Sesuai dengan namanya yaitu RecyclerView. Jadi, view yang ada dalam RecyclerView akan mengalami Recycler (Daur Ulang) atau bahasa gampangnya bisa dipakai lagi view-nya. Agak aneh ya bahasanya. Gambar berikut akan menjelaskannya.
Jadi, awalnya screen kita cuma menampilkan 4 item view dan si RecyclerView membuat 4 item view (data 1 s.d. data 4). Lalu, kita scroll dan tampil item view 2 s.d. item view 5 (item view 5 dibuat oleh si RecyclerView) atau data 2 s.d. data 5. Lalu, kita scroll lagi dan tampil item view 3 s.d. item view 6 (item view 6 dibuat oleh si Recyclerview) atau data 3 s.d. data 6. Lalu, kita scroll lagi, muncul item view 4 s.d. 7 (item view 7 merupakan hasil daur ulang dari item view 1) atau data 4 s.d. data 7. Memang view-nya menampilkan data ke berikutnya yang menunjukkan seolah-olah itu terbuat dari awal namun, kenyataannya adalah view-nya merupakan hasil daur ulang dari item view sebelumnya dimana, pada gambar diatas adalah item view 1.
Give Me a Project
Okay, untuk contoh projeknya kita akan membuat aplikasi yang sama persis seperti pada gambar sebelumnya yaitu, menampilkan list data siswa untuk diabsensi.
Silakan buat layout berikut di activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
android:padding="16dp"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Lalu, kita buat layout untuk si item RecyclerView-nya dan nama file-nya kita namakan item_adapter.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/text_view_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:maxLines="1"
android:singleLine="true"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/radio_group_absensi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_view_name"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/radio_button_hadir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hadir" />
<RadioButton
android:id="@+id/radio_button_absen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Absen" />
<RadioButton
android:id="@+id/radio_button_sakit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Sakit" />
</RadioGroup>
</RelativeLayout>
Selanjutnya, kita buat file model-nya dengan nama Kehadiran.
data class Kehadiran (
val nama: String = "",
var tipeKehadiran: String = ""
)
Berikutnya, kita buat file adapter-nya dengan nama AdapterAbsensi
class AdapterAbsensi constructor(private val listKehadiran: MutableList<Kehadiran>) : RecyclerView.Adapter<AdapterAbsensi.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_adapter, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setIsRecyclable(false) // Tell to RecyclerView that's not recycleable
val kehadiran = listKehadiran[position]
holder.textViewName.text = kehadiran.nama
when (kehadiran.tipeKehadiran) {
"hadir" -> {
holder.radioButtonHadir.isChecked = true
}
"absen" -> {
holder.radioButtonAbsen.isChecked = true
}
"sakit" -> {
holder.radioButtonSakit.isChecked = true
}
else -> {
/* nothing to do in here */
}
}
}
override fun getItemCount(): Int = listKehadiran.size
inner class ViewHolder constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName: TextView = itemView.findViewById(R.id.text_view_name)
private val radioGroupAbsensi: RadioGroup = itemView.findViewById(R.id.radio_group_absensi)
val radioButtonHadir: RadioButton = itemView.findViewById(R.id.radio_button_hadir)
val radioButtonAbsen: RadioButton = itemView.findViewById(R.id.radio_button_absen)
val radioButtonSakit: RadioButton = itemView.findViewById(R.id.radio_button_sakit)
init {
radioGroupAbsensi.setOnCheckedChangeListener { _, id ->
when (id) {
R.id.radio_button_hadir -> {
listKehadiran[adapterPosition].tipeKehadiran = "hadir"
}
R.id.radio_button_absen -> {
listKehadiran[adapterPosition].tipeKehadiran = "absen"
}
R.id.radio_button_sakit -> {
listKehadiran[adapterPosition].tipeKehadiran = "sakit"
}
else -> {
/* nothing to do in here */
}
}
}
}
}
}
And finally, kita set adapter-nya si RecyclerView di file MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listKehadiran = mutableListOf<Kehadiran>()
listKehadiran.add(Kehadiran("Yudi Setiawan"))
listKehadiran.add(Kehadiran("Ayu Lestari"))
listKehadiran.add(Kehadiran("Icha"))
listKehadiran.add(Kehadiran("Putra"))
listKehadiran.add(Kehadiran("Putri"))
listKehadiran.add(Kehadiran("Anindya"))
listKehadiran.add(Kehadiran("Mayuko"))
listKehadiran.add(Kehadiran("Yoko"))
listKehadiran.add(Kehadiran("Surya"))
listKehadiran.add(Kehadiran("Deka"))
listKehadiran.add(Kehadiran("Fitriani"))
listKehadiran.add(Kehadiran("Rizqy"))
listKehadiran.add(Kehadiran(("Lili")))
listKehadiran.add(Kehadiran("Ali"))
listKehadiran.add(Kehadiran("Albert"))
listKehadiran.add(Kehadiran("Indah"))
listKehadiran.add(Kehadiran("Silvina"))
listKehadiran.add(Kehadiran("Agung"))
listKehadiran.add(Kehadiran("Bobby"))
val adapterAbsensi = AdapterAbsensi(listKehadiran)
recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapterAbsensi
}
}
Sekarang silakan coba kita jalankan program diatas maka, hasilnya pasti akan sama seperti problem yang saya jelaskan tadi.
Solution
Untuk solusinya adalah kita bisa pakai attribute setIsRecycleable
agar si RecyclerView-nya tidak menggunakan view daur ulang. Silakan tambahkan kode attribute tersebut kedalam method onBindViewHolder
.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setIsRecyclable(false)
val kehadiran = listKehadiran[position]
....
holder.textViewName.text = kehadiran.nama
....
}
Sekarang coba jalankan lagi programnya.
Dan akhirnya terselesaikan juga problem diatas. So give applause for all.
Another Solution
Selain solusi pertama diatas, sebenarnya ada juga solusi lainnya yaitu kita mempertahankan state setiap view-nya didalam model dan solusi yang sebelumnya sebenarnya tidaklah saya rekomendasikan. Untuk solusi dengan odel silakan kita buka file AdapterAbsensi dan ubah menjadi seperti berikut.
class AdapterAbsensi constructor(private val listKehadiran: MutableList<Kehadiran>) : RecyclerView.Adapter<AdapterAbsensi.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_adapter, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val kehadiran = listKehadiran[position]
holder.textViewName.text = kehadiran.nama
when (kehadiran.tipeKehadiran) {
"hadir" -> {
holder.radioButtonHadir.isChecked = true
}
"absen" -> {
holder.radioButtonAbsen.isChecked = true
}
"sakit" -> {
holder.radioButtonSakit.isChecked = true
}
else -> {
holder.radioGroupAbsensi.clearCheck()
}
}
}
override fun getItemCount(): Int = listKehadiran.size
inner class ViewHolder constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName: TextView = itemView.findViewById(R.id.text_view_name)
val radioGroupAbsensi: RadioGroup = itemView.findViewById(R.id.radio_group_absensi)
val radioButtonHadir: RadioButton = itemView.findViewById(R.id.radio_button_hadir)
val radioButtonAbsen: RadioButton = itemView.findViewById(R.id.radio_button_absen)
val radioButtonSakit: RadioButton = itemView.findViewById(R.id.radio_button_sakit)
init {
radioGroupAbsensi.setOnCheckedChangeListener { radioGroup, id ->
when (radioGroup.checkedRadioButtonId) {
R.id.radio_button_hadir -> {
listKehadiran[adapterPosition].tipeKehadiran = "hadir"
}
R.id.radio_button_absen -> {
listKehadiran[adapterPosition].tipeKehadiran = "absen"
}
R.id.radio_button_sakit -> {
listKehadiran[adapterPosition].tipeKehadiran = "sakit"
}
else -> {
listKehadiran[adapterPosition].tipeKehadiran = ""
}
}
}
}
}
}