Meningkatkan Peforma Recyclerview dengan DiffUtil
Halo folks, belakangan ini saya mendapat kesempatan untuk sharing disebuah event yang membahas topik mengenai dasar pengembangan aplikasi android. Dimana saat membahas List tentu disitu saya menjelaskan mengenai RecyclerView implementation dan saat sesi diskusi berlangsung terdapat salah satu peserta yang bertanya mengenai apakah mungkin sebuah Recyclerview ditingkatkan performanya. Dan menurut saya walaupun ini bukan sebuah teknologi yang tergolong baru dan mungkin hampir seluruh developer android sudah mengimplementasikannya, namun topik ini tetap menarik untuk saya coba bahas pada artikel ini.
Menampilkan sebuah list data pada aplikasi pasti tidak bisa dihindari ketika mengembangkan sebuah aplikasi. Karena hampir semua tipe apps pasti menampilkan list data. Untuk membuat list data ada dua pilihan yang dapat diambil oleh developer, yaitu menggunakan ListView atau RecyclerView. Dan pasti pilihan kita jatuh pada RecyclerView. Karena RecyclerView dianggap lebih powerful dan lebih disarankan untuk digunakan dibandingkan dengan ListView dengan beberapa alasan.
- Lebih efisien dalam menampilkan list item, karena RecyclerView hanya menampilkan ulang item yang terlihat di layar, sementara ListView harus menampilkan ulang seluruh list item.
- Mendukung lebih banyak layout manager untuk menampilkan list item, termasuk layout dengan orientasi horizontal, grid, dan staggered grid.
- Selain itu juga dapat menghandle swipe dan drag and drop action dengan lebih mudah melalui kelas ItemTouchHelper.
- Lebih mudah di-custom dan diintegrasikan dengan fitur-fitur lain dalam Android, seperti animasi dan data binding.
Lalu Recyclerview yang sudah dianggap powerful untuk menampilkan list data masih bisa ditingkatkan lagi performanya?
Tentu saja bisa, dengan menggunakan sebuah kelas utilitas yaitu DiffUtil. DiffUtil sendiri merupakan util class yang disediakan oleh Android untuk membantu membandingkan dua list data dan menghitung perbedaan (diff) antara kedua list tersebut. Dengan begitu RecyclerView hanya akan mengupdate item-item yang mengalami perubahan, bukan memperbaharui seluruh RecyclerView item. Selain efisiensi karena RecyclerView hanya akan mengupdate pada item-item yang berubah, sehingga meningkatkan performa aplikasi kita. Kemampuan DiffUtil yang dapat menghandle perubahan pada item-item seperti insert, delete, dan shorting item dengan mudah dan efisien jelas memberikan keuntungan bagi kita dari segi fungsionalitas. Selain itu dari segi scalability, list data yang besar juga dapat dikelola dengan baik oleh DiffUtil, karena mampu menangani update data dengan cepat dan efisien.
Saat ini saat mengimplementasikan Recyclerview untuk mengaupdate list dengan data set baru, kita menggunakan notifyDataSetChanged(). Saat menggunakan notifyDataSetChanged() sebenarnya Recyclerview tidak mengetahui bagian item mana yang perlu diupdate sehingga Recyclerview akan membuat ulang seluruh item yang terlihat dilayar. Dan dalam proses ini akan terbuat sebuah instance baru adaptor yang kita miliki, yang akan membuat prosesnya sangat berat.
Untuk mengimprove proses ini DiffUtil akan mengbandingkan list lama dan list baru dengan menggunakan DiffUtil.Callback, sehingga akan diketahui item mana yang perlu diupdate. Sehingga Recyclerview tidak membuat ulang seluruh list item yang tampil dilayar tetapi akan menampilkan list lama sebagai list baru dengan memperbaharui item tertentu yang perlu di update saja. Dan untuk meng-notify Recyclerview saat terjadi perubahan, DiffUtil memiliki beberapa method yang dapat kita gunakan untuk mempermudah proses ini :
- notifyItemMoved
- notifyItemRangeChanged
- notifyItemRangeInserted
- notifyItemRangeRemoved
Simple Project
Untuk memahami lebih dalam terkait dengan implementasi DiffUtil, mari langsung saja kita coba :)
- Buatlah sebuah project baru
- Tambahkan RecyclerView pada activity_main.xml, seperti dibawah ini.
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_news"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
3. Sebelumnya tambahkan string berikut pada file res → string yang nantinya akan kita gunakan sebagai placeholder pada layout item.
<string name="lorem_ipsum">Lorem ipsum</string>
4. Buatlah layout untuk item list item_news.xml
5. Tambahkan kode untuk layout item seperti dibawah ini.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:elevation="10dp"
app:cardCornerRadius="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:text="@string/lorem_ipsum"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_category"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/lorem_ipsum"
app:layout_constraintEnd_toStartOf="@+id/tv_date"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
<TextView
android:id="@+id/tv_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/lorem_ipsum"
app:layout_constraintBottom_toBottomOf="@+id/tv_category"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/tv_category"
app:layout_constraintTop_toTopOf="@+id/tv_category"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
6. Lalu buatlah sebuah file untuk sebuah DataClass sebagai model.
7. Dan tambahkan kode seperti dibawah ini.
data class News(
val id: Int,
val title: String,
val category: String,
val date: String
)
8. Create new file NewsData, and add the sample data like below
object NewsData {
fun loadData() : MutableList<News>{
val newsList : MutableList<News> = mutableListOf()
newsList.add(News(1,"Rusia Bentuk Pasukan Pemburu Tank, Lawan Senjata Barat di Ukraina", "International", "11 Apr 2023"))
newsList.add(News(2,"Dokumen Militer AS Bocor Disebut Sangat Bahaya bagi Ukraina", "International", "11 Apr 2023"))
newsList.add(News(3,"Apa Beda Masjid Al Aqsa dan Dome of the Rock di Yerusalem?", "International", "11 Apr 2023"))
newsList.add(News(4,"FOTO: Tegang dan Dekap Duka usai Penembakan di Bank Louisville AS", "International", "11 Apr 2023"))
newsList.add(News(5,"Pelaku Penembakan di Bank Kentucky AS Beraksi Sambil Live Streaming", "International", "11 Apr 2023"))
newsList.add(News(6,"Pelaku Penembakan di Bank Kentucky AS Beraksi Sambil Live Streaming", "International", "11 Apr 2023"))
newsList.add(News(7,"Presiden Taiwan Damprat China Latihan Perang: Tak Bertanggung Jawab", "International", "11 Apr 2023"))
newsList.add(News(8,"VIDEO: Penampakan Latihan Tembak Kapal Perang China di Perairan Taiwan", "International", "11 Apr 2023"))
newsList.add(News(9,"Profil Dalai Lama, Pemimpin Buddha yang Tersangkut Skandal", "International", "11 Apr 2023"))
newsList.add(News(10,"1.500 Pemukim Israel Serbu Kompleks Masjid Al Aqsa Dikawal Polisi", "International", "11 Apr 2023"))
newsList.add(News(11,"Gambar Purba Misterius di Gurun Qatar, Cek Deret Teori soal Fungsinya", "Teknologi", "11 Apr 2023"))
newsList.add(News(12,"Rekomendasi Kulkas Cantik dan Muat Banyak", "Teknologi", "11 Apr 2023"))
newsList.add(News(13,"Daftar Aplikasi Pengingat Imsak dan Buka Puasa Ramadhan 2023", "Teknologi", "11 Apr 2023"))
newsList.add(News(14,"Lubang Hitam Tertua Ada di Pusat Galaksi, Diduga Bukan Satu-satunya", "Teknologi", "11 Apr 2023"))
newsList.add(News(15,"FOTO: Pesona Pohon-pohon Berbatu di Zanda Earth Forest Tibet", "Teknologi", "11 Apr 2023"))
newsList.add(News(16,"2 Wilayah Terbaik Pantau Gerhana Matahari Total 20 April", "Teknologi", "11 Apr 2023"))
newsList.add(News(17,"FOTO: Danau Kering di Austria Jadi Saksi Bumi Kian Panas", "Teknologi", "11 Apr 2023"))
newsList.add(News(18,"Google Search Bakal Pakai Chatbot AI, Efek ChatGPT?", "Teknologi", "11 Apr 2023"))
newsList.add(News(19,"VIDEO: Melihat Pelepasan Puluhan Kura-kura Raksasa di Galapagos", "Teknologi", "11 Apr 2023"))
newsList.add(News(20,"Beda Nasib Dua Bibit Siklon Tropis Dekat RI, Simak Efeknya", "Teknologi", "11 Apr 2023"))
return newsList
}
}
9. Lalu buat sebuah adapter recyclerview dengan nama NewsAdapter, lalu tambahkan kode berikut.
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
private lateinit var itemBinding: ItemNewsBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsAdapter.NewsViewHolder {
itemBinding = ItemNewsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NewsViewHolder()
}
override fun onBindViewHolder(holder: NewsAdapter.NewsViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
inner class NewsViewHolder : RecyclerView.ViewHolder(itemBinding.root) {
fun setData(item: News) {
itemBinding.apply {
tvTitle.text = item.title
tvCategory.text = item.category
tvDate.text = item.date
}
}
}
}
10. Sekarang saatnya menambahkan DiffUtil, pada adapter diatas. Pertama — tama tambahkan DiffUtil.Callback untuk membandingkan antara data pada list lama dan list yang akan ditampilkan nantinya. Tambahkan code seperti dibawah ini.
private val differCallback = object : DiffUtil.ItemCallback<News>(){
override fun areItemsTheSame(oldItem: News, newItem: News): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: News, newItem: News): Boolean {
return oldItem == newItem
}
}
11. Agar proses ini dapat dieksekusi di background wrap dengan menggunakan AsyncListDiffer seperti dibawah ini.
val differ = AsyncListDiffer(this, differCallback)
12. Selanjutnya pada method onBindViewHolder tambahkan kode berikut.
override fun onBindViewHolder(holder: NewsAdapter.NewsViewHolder, position: Int) {
holder.setData(differ.currentList[position])
holder.setIsRecyclable(false)
}
13. Dan pada method getItemCount tambahkan kode berikut.
override fun getItemCount(): Int = differ.currentList.size
14. Selanjutnya kita berpindah ke MainActivity class. Langkah pertama inisialisasikan ActivityMainBiding sebagai content view.
private lateinit var mainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
...
}
15. Tambahkan adapter pada recyclerview seperti dibawah ini.
private val newsAdapter by lazy { NewsAdapter() }
newsAdapter.differ.submitList(NewsData.loadData())
mainBinding.rvNews.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = newsAdapter
}
Dan untuk full codenya seperti dibawah ini.
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
private val newsAdapter by lazy { NewsAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
newsAdapter.differ.submitList(NewsData.loadData())
mainBinding.rvNews.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = newsAdapter
}
}
}
Cukup mudah bukan ? :) untuk fullcode bisa akses kesini.
Kesimpulannya dengan menggunakan DiffUtil, kita dapat memastikan bahwa RecyclerView akan bekerja secara efisien dan hanya melakukan update pada item-item yang benar-benar berubah, sehingga membuat aplikasi akan lebih responsif dan hemat sumber daya.
Jangan lupa follow akun saya untuk mendapat update artikel seputar android, dan clap jika menurut anda artikel ini bermanfaat. 😉