This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

I’m sure every Android developer used RecyclerView, but did you use it elegantly enough?

Anyone who has experimented with Compose or DataBinding, or even Vue and React on the front end, will feel the elegance of declarative UI. In this article, I want to give you a fresh perspective on a familiar thing by using different ideas of imperative and declarative code, which we often use in RV. I hope to help you understand the concept of declarative.

Imperative RV

I’m going to choose a classic TODO scenario to unfold. First, we define a data class todo with three attributes: ID, Content, and done.

data class Todo(var id:int,var content:String,var done = false)
Copy the code

The UI is as follows. The code is very simple so I won’t post it.

Write another RV Adapter, and the preliminary work is done.

Myadapter.kt (code appropriate deletion)

class MyAdapter(var list: MutableList<Todo>) : RecyclerView.Adapter<ViewHolder>() { override fun onBindViewHolder(viewHolder: ViewHolder, position: Int data = list[position] Int data = list[position]Copy the code

This allows us to display the to-do list in our Activity. And then we have some requirements that we need to change the state of an item, add or remove an item.

An imperative idea would be to add operator functions to MyAdapter to add, remove, or change state. The code is as follows:

Myadapter.kt (code appropriate deletion)

class MyAdapter(var list):Adapter(){
    fun updateItemDone(index:Int){
        list[index].done = true
        notifyItemChanged(index)
    }
    fun updateItemTodo(index:Int){
        list[index].done = false
        notifyItemChanged(index)
    }
    fun addItem(data:Todo){
        list.add(data)
        notifyItemInserted(list.size - 1)
    }
    fun deleteItem(index:Int){
        list.remove(index)
        notifyItemRemoved(index)
    }
}
Copy the code

The function is written, and then the corresponding function is called in a few click event callbacks, so the requirement is fulfilled, and everything seems to be quiet

So let’s look at declarative thinking.

Declarative RV

Myadapter.kt (code appropriate deletion)

class MyAdapter() : ListAdapter<Todo, ViewHolder>(diffCallback) { { override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {val data = getItem(position)}} Object diffCallback: DiffUtil.ItemCallback<Todo>() { override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean = oldItem.content == newItem.content && oldItem.done == newItem.done }Copy the code

Refer to the code below for invocation.

Mainactivity.kt (code deleted)

class MainActivity(){
    fun initView(){
        //...
        setAdapterData(myAdapter,list)
        rv.setAdapter(myAdapter)
    }
    
    fun setAdapterData(adapter:ListAdapter,list:List<Todo>){
        adapter.submitList(list)
    }
}
data class Todo(val id:int,val content:String,val done = false)
Copy the code

Wouldn’t it be nice to have just one setAdapterData function to do all the todos with the above code? Of course, there are a lot of things in the code that might surprise you, and we’ll go through them.

The main difference between the two codes is the use of the ListAdpater class, because all you need to do is make changes to the original data, and then pass the new data through the setAdapterData function, whether you delete or add, modify or move. So how does ListAdapter do this? Look at the DiffUtil.itemCallback, which is used to compare the difference between two data classes. The result is calculated by it and passed to the ListAdapter. The ListAdapter can add, delete, modify, move, etc., the RV item according to the difference.

This makes the RV easier, but what does it have to do with the declarative? Details will be explained in the next article, stay tuned 🙂