Getting Rid of the RecyclerView.Adapter Routine with a DataBinding
Getting Rid of the RecyclerView.Adapter Routine with a DataBinding
RecyclerView is the main UI element of almost any application. Writing adapters and ViewHolders is often too routine and contains enough boilerplate code. In this article I want to show how with the help of DataBinding and the MVVM pattern you can write an abstract adapter and completely forget about ViewHolders, inflate, manual binding and other routine.
ViewHolder
We are all used to writing a separate ViewHolder for each type of cell in the table for storing links to individual views and data binding.
We can say that DataBinding generates on-the-fly the code that you usually write in ViewHolders, so there is no need for them and we can easily use one implementation that stores the finished binding object:
abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = DataBindingUtil.bind<ViewDataBinding>(view)
}
}
ViewDataBinding is a basic abstract class for all generated DataBinding classes, and even though we pass it a template parameter for the bind method, DataBindingUtil will itself understand which layout we use and which implementation to use as a result.
ViewModelAdapter
Having dealt with ViewHolder, we need to decide what we want from our base adapter in the end. All I need from the adapter within the MVVM architecture is to give a list of objects (ViewModels), tell me what markup I want to use for the data in this list of classes, and not worry at all about the necessary logic.
DataBinding takes the logic of data binding, but this is a completely different article, of which there are already enough on the Internet.
Let’s write the logic for the configuration of our adapter:
abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { data class CellInfo(val layoutId: Int, val bindingId: Int) protected val items = LinkedList<Any>() private val cellMap = Hashtable<Class<out Any>, CellInfo>() protected fun cell(clazz: Class<out Any>, @LayoutRes layoutId: Int, bindingId: Int) {
cellMap[clazz] = CellInfo(layoutId, bindingId)
} protected fun getCellInfo(viewModel: Any): CellInfo {
cellMap.entries
.filter { it.key == viewModel.javaClass }
.first { return it.value } throw Exception("Cell info for class ${viewModel.javaClass.name} not found.")
}}
For each class of table objects, we will store a pair of layoutId and bindingId.
- layoutId — as is clear from the name and annotation @LayoutRes is the corresponding cell layout.
- bindingId is the generated variable identifier used in the corresponding markup. We will need it in order to bind the table object to the ViewHolder written earlier, or rather to ViewDataBinding.
It remains only to implement the abstract functions of RecyclerView.Adapter:
abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { override fun getItemCount(): Int = items.size override fun getItemViewType(position: Int): Int {
return getCellInfo(items[position]).layoutId
} override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(viewType, parent, false)
return ViewHolder(view)
} override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
if (holder != null) {
val cellInfo = getCellInfo(items[position])
if (cellInfo.bindingId != 0)
holder.binding.setVariable(cellInfo.bindingId, items[position])
}
}}
- getItemViewType — since layoutId is unique for different cells, we can easily use it as viewType.
- onCreateViewHolder — do not forget that viewType is our layoutId.
- onBindViewHolder — all that is required to bind the data of the object to the markup is to inform the DataBinding that in this cell there is now a new object, it will take care of the rest of the logic.
On this, the whole basic logic of the ViewModelAdapter is described, however, there remains one problem — the processing of clicks on cells. Usually this logic is described in Activity, but I’m not a fan of translating logic up the hierarchy, if you can’t do without it, therefore, I implement it directly in the adapter, but you can implement it wherever you want.
To implement click processing, we will add a concept such as sharedObject to the ViewModelAdapter, an object that will be bound to all cells of the table (not necessary if nothing is found in the markup with the given bindingID).
abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { private val sharedObjects = Hashtable<Int, Any>() protected fun sharedObject(sharedObject: Any, bindingId: Int) {
sharedObjects[bindingId] = sharedObject
} override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(viewType, parent, false)
val viewHolder = ViewHolder(view) sharedObjects.forEach { viewHolder.binding.setVariable(it.key, it.value) } return viewHolder
}}
Now let’s see how it all finally works:
As an example, I implemented an adapter for the side menu (use the NavigationView from the standard library if you do not need to move away from Material Design).
object NavigationAdapter : ViewModelAdapter() { init {
cell(NavigationHeaderViewModel::class.java, R.layout.cell_navigation_header, BR.vm)
cell(NavigationItemViewModel::class.java, R.layout.cell_navigation_item, BR.vm)
cell(NavigationSubheaderViewModel::class.java, R.layout.cell_navigation_subheader, BR.vm) sharedObject(this, BR.adapter)
} override fun reload(refreshLayout: SwipeRefreshLayout?) {
items.clear()
items.add(NavigationHeaderViewModel)
items.add(NavigationItemViewModel(R.drawable.ic_inbox_black_24dp, "Inbox"))
items.add(NavigationItemViewModel(R.drawable.ic_star_black_24dp, "Starred"))
items.add(NavigationItemViewModel(R.drawable.ic_send_black_24dp, "Sent mail"))
items.add(NavigationItemViewModel(R.drawable.ic_drafts_black_24dp, "Drafts"))
items.add(NavigationSubheaderViewModel("Subheader"))
items.add(NavigationItemViewModel(R.drawable.ic_mail_black_24dp, "All mail"))
items.add(NavigationItemViewModel(R.drawable.ic_delete_black_24dp, "Trash"))
items.add(NavigationItemViewModel(R.drawable.ic_report_black_24dp, "Spam"))
notifyDataSetChanged()
} fun itemSelected(view: View, model: NavigationItemViewModel) {
Toast.makeText(view.context, "${model.title} selected!", Toast.LENGTH_SHORT).show()
}}
And as an example layout: cell_navigation_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> <data> <variable
name="vm"
type="com.github.akvast.mvvm.ui.vm.NavigationItemViewModel" /> <variable
name="adapter"
type="com.github.akvast.mvvm.ui.adapter.NavigationAdapter" />
</data> <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:onClick="@{v -> adapter.itemSelected(v, vm)}"> <ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp"
android:src="@{vm.icon}"
android:tint="@{@color/grey_600}" /> <TextView
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="48dp"
android:paddingBottom="12dp"
android:paddingLeft="72dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:text="@{vm.title}"
tools:text="Item title" /> </FrameLayout></layout>
As you can see, everything is quite simple, there is no unnecessary logic. We can declare as many cell types as you like by calling 1 function. We can forget about manually binding data for the UI.
This adapter successfully undergoes combat tests for six months in several large projects.
I will be happy to answer your questions in the comments.
useful links
→ Full code and example project on GitHub
→ ViewModelAdapter written in Java
→ Official documentation on DataBinding
→ Configuring the use of DataBinding and other libraries in Kotlin
→ https://habr.com/ru/post/337110/
You have followed the group:
ใครสนใจ มา join line ได้ครับ รับจำนวนจำกัด ใครปัญหา ชอบแชร์ มีเรื่องอะไรใหม่ๆ สามารถ join เข้าได้เลยครับ 😁
หากสนใจ ก็สามารถ join ได้ที่นี้เลยครับ . หรือ scan qrcode . ไปได้เลย
[Android Github Dev] คุณได้รับคำเชิญให้เข้าร่วมสแควร์ของ LINE
https://line.me/ti/g2/UVVDK6Z5EE
Android Developer
Thank you for joining: https://www.facebook.com/groups/883546485084033/?fref=ts I created a group of Android Developers Android and Kotlin Droidcon Github library. If you have any questions, you can ask.
You can join in the App Telegram. https://t.me/AndroidDevelopersAndKotlin .
Now join Android Developers And Kotlin Droidcon Github Library Community Telegram App to help each other. Joining Link:
เข้าร่วม Group Line: เข้าร่วมกลุ่ม
: กล่องจดหมายเพื่อส่งอีเมล pongku71@gmail.com
Android Open Source Projects [inclusion] [SUM] [Join user groups] There is a problem with Android projects and want to give answers. What's new or want to update the robot continuously. Can join. Line: po56789 or inbox on the page, come to join, please just !!!!
Thank you
แฟนเพจ PongPloy Zone AppDev
Link : https://www.facebook.com/PPAndroid-Github-Dev-Zone-170227463027435/notifications/
Language learning application APP .
EN
Practice writing, reading, Kai-ABC, this application. Designed to be easy to use, uncomplicated, with illustrations and sound for train children to read according to Thai-English consonants clearly.Practice writing all 44 Thai consonants from chicken to hawk and 26 English consonants since A-Z makes it easy to learn and remember.This application is suitable for Thai students. And foreigners studying Thai languageAnd in the future, may add more games to children
Thai :
ฝึกเขียน อ่าน ก ไก่-ABC แอพพลิเคชั่นนี้ ออกแบบมาให้ใช้งานง่าย ไม่ซับซ้อน โดยมีภาพประกอบพร้อมเสียงสำหรับฝึก ให้เด็กๆ ท่องตาม อ่านเสียงพยัญชนะ ไทย — อังกฤษ ได้ชัดเจน ฝึกเขียนพยัญชนะไทยทั้ง 44 ตัว ตั้งแต่ ก ไก่ จนถึง ฮ นกฮูก และ พยัญชนะภาษาอังกฤษทั้ง 26 ตัว ตั้งแต่ A-Z ทำให้ง่ายต่อการเรียนรู้และจดจำแอพพลิเคชั่นนี้เหมาะสำหรับเด็กนักเรียนไทย และชาวต่างชาติที่ศึกษาภาษาไทยและต่อไปในอนาคต อาจจะเพิ่ม เกม ให้เด็กมาสนใจมากขึ้น
Download : App link : https://play.google.com/store/apps/details?id=com.pongploydev.education.mediaapp