Lightweight DataBinding for Android

Hello dear readers. We all love and use the DataBinding , which Google introduced a few years ago, to link the data model to views through the ViewModel. In this article, I want to share with you how you can unify this process using the Kotlin language, and fit the creation of adapters for RecyclerView (hereinafter RV), ViewPager and ViewPager2 in several lines of code.





To begin with, we used to develop custom adapters that were created by ViewHolders under the hood, and their writing, and even more support, took quite a lot of time. Below is an example of a typical RV adapter:





class CustomAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {    
    
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView        
        init {
            textView = view.findViewById(R.id.textView)
        }
    }   
    
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.text_row_item, viewGroup, false)        
      return ViewHolder(view)
    }    
     
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {        // Get element from your dataset at this position and replace the
        viewHolder.textView.text = dataSet[position]
    }    
    
    override fun getItemCount() = dataSet.size
}
      
      



As the project grows, there may be many more of these adapters. I remember once, the adapter was so huge, several hundred lines of code, that it took a colossal amount of time to figure out what was happening there, and even more to add something new, since it worked with different data models, and also had to create different mappings for each data type. To be honest, it was hard.





DataBinding , , onCreateViewHolder



, LayoutInflater



, DataBindingUtil.inflate



, .





class BindingViewHolder(val binding: ItemTextRowBinding) : RecyclerView.ViewHolder(binding.root)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val binding = DataBindingUtil.inflate<ItemTextRowBinding>(LayoutInflater.from(parent.context), viewType, parent, false)
        val viewHolder = BindingViewHolder(binding)
        return viewHolder
}

override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {    
  holder.binding.setVariable(BR.item, dataSet[position])
}
      
      



, RV, , . BindingAdapter androidx.databinding. , , RV, - DataBindingRecyclerViewConfig



, .





, EasyRecyclerBinding. BindingAdapters ViewPager ViewPager2. :

1) , , , RV, - app:items



app:rv_config



.





<?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">

    <data>
    	<variable
        	name="vm"
        	type="com.rasalexman.erb.ui.base.ExampleViewModel" />

        <variable
            name="rvConfig"
            type="com.rasalexman.easyrecyclerbinding.DataBindingRecyclerViewConfig" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:items="@{vm.items}"
            app:rv_config="@{rvConfig}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:listitem="@layout/item_recycler"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
      
      



ViewModel, , , RV, DataBindingRecyclerViewConfig.





//       
class ExampleViewModel : ViewModel() {  
   val items: MutableLiveData<MutableList<RecyclerItemUI>> = MutableLiveData()
}

data class RecyclerItemUI(
    val id: String,
    val title: String
)
      
      



, , .





<?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">

    <data>

        <variable
            name="item"
            type="com.rasalexman.erb.models.RecyclerItemUI" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/list_selector_background">

        <TextView
            android:id="@+id/titleTV"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingStart="16dp"
            android:paddingTop="8dp"
            android:paddingEnd="16dp"
            android:textColor="@color/black"
            android:textSize="18sp"
            android:text="@{item.title}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Hello world" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingStart="16dp"
            android:paddingEnd="16dp"
            android:paddingBottom="8dp"
            android:textColor="@color/gray"
            android:textSize="14sp"
            android:text="@{item.id}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/titleTV"
            tools:text="Hello world" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
      
      



2) dataBinding, - createRecyclerConfig<I : Any, BT : ViewDataBinding>



, DataBindingRecyclerViewConfig, id , , .





class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, ExampleViewModel>() {    
  override val layoutId: Int get() = R.layout.rv_example_fragment
  override val viewModel: ExampleViewModel by viewModels()    
  
  override fun initBinding(binding: RvExampleFragmentBinding) {
        super.initBinding(binding)
        binding.rvConfig = createRecyclerConfig<RecyclerItemUI, ItemRecyclerBinding> {
            layoutId = R.layout.item_recycler
        		itemId = BR.item            
        }
    }
}
      
      



, , ViewModel RV. , onItemClick, onItemCreate, onItemBind



  .





, , EasyRecyclerBinding - IBindingModel



  layoutResId



, - id , .





data class RecyclerItemUI(    
		val id: String,
    val title: String
) : IBindingModel {    
    override val layoutResId: Int        
    		get() = R.layout.item_recycler
}
data class RecyclerItemUI2(
    val id: String,
    val title: String
) : IBindingModel {
    override val layoutResId: Int
        get() = R.layout.item_recycler2
}
      
      



, , - createRecyclerMultiConfig



, .





class RecyclerViewExampleFragment : BaseBindingFragment<RvExampleFragmentBinding, RecyclerViewExampleViewModel>() {    
  override val layoutId: Int get() = R.layout.rv_example_fragment
    override val viewModel: RecyclerViewExampleViewModel by viewModels()
    
    override fun initBinding(binding: RvExampleFragmentBinding) {
        super.initBinding(binding)
        binding.rvConfig = createRecyclerMultiConfig {
            itemId = BR.item
        }
    }
}

class RecyclerViewExampleViewModel : BasePagesViewModel() {
    open val items: MutableLiveData<MutableList<IBindingModel>> = MutableLiveData()
}
      
      



, RV, , , , , presentation . , , , , .







A similar process for creating adapters for ViewPager and ViewPager2 is presented in an example on github along with open source, a link to which I posted at the end of the article. At the moment, the library is still being finalized, and I would like to receive adequate feedback and wishes for its further development. It also includes auxiliary functions for conveniently creating biding, including in conjunction with the ViewModel. (LayoutInflater.createBinding, Fragment.createBindingWithViewModel, etc)





Thanks for reading to the end. Enjoy coding and good mood)








All Articles