This chapter, the preface

This article is an extension of the kotlin coroutine. Those interested in kotlin coroutines can read the following links.

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code
  • A ViewModel wrapper for everyday use

The author is just an ordinary developer, the design is not necessarily reasonable, we can absorb the essence of the article, to dross.

We are now ready to do some basic encapsulation and start using dataBinding in our app’s Bulid. gradle file

android {
    buildFeatures {
        dataBinding  = true
    }
   / / to omit...
}
Copy the code

Based on theDataBindingThe encapsulation

Let’s start by creating a simple layout file, activity_main.xml. To save time, we’ll also create a fragment_main.xml to keep the same layout.


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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.MainActivity">

        <Button
            android:id="@+id/btn"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="Hello World"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

When we use DataBinding to initialize a layout, we usually like to use the following methods in an Activity:

class MainActivity : AppCompatActivity() {
 private lateinit var mBinding:ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
         mBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
    }
}
Copy the code

In the Fragment:

class HomeFragment:Fragment() {

    private lateinit var mBinding:FragmentMainBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        mBinding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_main,container,false)
        return mBinding.root
    }
}
Copy the code

In this case, you need to rewrite each activity and Fragment you create. So we create a BaseActivity for abstraction, then use generics to pass in the ViewDataBinding object we need, VB, and get the LayoutRes resource through the constructor or abstract method

abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes resId:Int) : AppCompatActivity() {
    lateinit var mBinding:VB
    override fun onCreate(savedInstanceState: Bundle?). {
       super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView<VB>(this,resId)
    }
    / /...
}
 / / or
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity() {
    lateinit var mBinding:VB
    @LayoutRes abstract fun getLayoutId(a):Int
    override fun onCreate(savedInstanceState: Bundle?). {
       super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView<VB>(this,getLayoutId())
    }
}
Copy the code

This time is not through us after the above processing, when we use it will be a lot more convenient. Maybe some people who have encapsulated this look at this and think, what are you talking about? I know all of this. I’m not creative. The author wants to say: don’t worry, what we want to talk about is not above thing, after all do things all need prelude matting drop.

Although after the above abstraction, we have reduced a few steps. However, the author also wrote that there was some trouble, because we still need to hand pass a LayoutRes resource to use. To further refine the code, we need to look at the implementation of the ActivityMainBinding.

We started DataBinding by using layout’s Activity_main.xml layout, DataBinding automatically generates an ActivityM for us in our project app/build/generated/data_binding_base_class_source_out/packname/ DataBinding The ainBinding class, let’s look at its implementation:

public abstract class ActivityMainBinding extends ViewDataBinding {
  @NonNull
   public final Button btn;

  protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
      TextView tv) {
    super(_bindingComponent, _root, _localFieldCount);
    this.btn = btn;
    this.recyclerView = recyclerView;
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot) {
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component);
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, DataBindingUtil.getDefaultComponent());
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null.false, component);
  }
    / / to omit...
}
Copy the code

You can see that there are four inflate methods in the ActivityMainBinding, and the last of them are loaded directly with our layout file activity_main.xml. So we want to simplify this further by loading our layout with the corresponding inflate method from the inflate method in the ActivityMainBinding via reflection. The code is as follows:

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
    val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java)
    return  inflate.invoke(null, inflater) as VB
}
Copy the code

We define an extension method that, by reflection, grabs the generic ViewBinding we want from our Class, and invoke calls the Inflate method of the ViewBinding. We then create an interface for the BaseActivity subclass to initialize the UI binding.

interface BaseBinding<VB : ViewDataBinding> {
    fun VB.initBinding(a)
}
Copy the code
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity(), BaseBinding<VB> {
 internal val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
       getViewBinding(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        mBinding.initBinding()
    }
}
Copy the code

Now we can implement our Activity by inheriting BaseActivity:

class MainActivity : BaseActivity<ActivityMainBinding>() {

    override fun ActivityMainBinding.initBinding(a) {
        Log.d("MainActivity"."btn  :${btn.text}")}}Copy the code
D/MainActivity: btn  :Hello World
Copy the code

Isn’t our code much cleaner and cleaner now? This not only saves us time writing a lot of repetitive code, but also makes our code more reasonable and beautiful.

It’s a little different from an Activity, because a Fragment needs to pass in a ViewGroup when creating a layout, so we’ll make a slight change.

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?).:VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
    val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean: :class.java)
    return inflate.invoke(null, inflater, container, false) as VB
}
Copy the code

There may be situations where some people need to set attachToRoot to True when creating a Fragment. In this case, you can just extend the Fragment and we won’t show it here. Similarly, let’s abstract a BaseFragment:

abstract class BaseFragment<VB : ViewDataBinding> :Fragment(),BaseBinding<VB> {
    protected lateinit var mBinding:VB
        private set
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        mBinding = getViewBinding(inflater,container)
        return mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        mBinding.initBinding()
    }
    
    override fun onDestroy(a) {
    super.onDestroy()
    // Remember to unbind here to avoid memory leaks
    if(::mBinding.isInitialized){ 
        mBinding.unbind()
    }
}
Copy the code
class HomeFragment:BaseFragment<FragmentMainBinding>() {
    override fun FragmentMainBinding.initBinding(a) {
        Log.d("HomeFragment"."btn  :${btn.text}")}}Copy the code

See here is not a lot of ease of mind, not only reduced the amount of code, forced grid also improved a lot, but also increased the XX technology group in the time to touch fish blowing water.

Of course, we can not only be satisfied with this, in the process of code code there is a lot of repeated work is our Adapter, we take the use of RecyclerView.Adapter as an example, suppose we create a simple HomeAdapter:

class HomeAdapter(private val data: List<String>? = null) : RecyclerView.Adapter<HomeAdapter.BindingViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val mBinding = DataBindingUtil.inflate<ItemHomeBinding>(LayoutInflater.from(parent.context), R.layout.item_home ,parent, false)
        return BindingViewHolder(mBinding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
        holder.binding.tv.text = data? .get(position) ? :""
        // Other bindings...
        holder.binding.executePendingBindings()
    }

    fun setData(a){
        // Refresh data...
    }

    override fun getItemCount(a): Int {
        return data? .size ? :0
    }

    class BindingViewHolder constructor(val binding: ItemHomeBinding) : RecyclerView.ViewHolder(binding.root)
}
Copy the code

Even with the simplest Adapter, we need a lot of verbose code, and if we add the Item click event, we have a lot more work to do. So now we’re going to solve this problem, where are we going to get rid of:

  1. unifiedAdapterInitialization of the.
  2. To simplify theonBindViewHolderThe use of.
  3. You have to create it again every time you remove itViewHolder.
  4. Unify our SettingsItemThe listening event mode of
  5. unifiedAdapterRefresh data of.

First we need to modify the extension getViewBinding we defined earlier, because we do not know which class getViewBinding is used for, and this class defines several generics. So we add a position argument with a default value of 0 instead of 0, and let the caller set the order in which the ViewBinding is placed:

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater,position:Int = 0):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
    val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java)
    return  inflate.invoke(null, inflater) as VB
}

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup? ,position:Int = 0):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
    val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean: :class.java)
    return inflate.invoke(null, inflater, container, false) as VB
}
Copy the code

To create our BaseAdapter, post the complete code:

abstract class BaseAdapter<T, VB : ViewDataBinding> : RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() {

    private var mData: List<T> = mutableListOf()

    fun setData(data: List<T>? {
        data? .let {val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(a): Int {
                    return mData.size
                }

                override fun getNewListSize(a): Int {
                    return it.size
                }

                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseAdapter.areItemsTheSame(oldData, newData)
                }

                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
                }
            })
            mData = data
            result.dispatchUpdatesTo(this)}? : let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size)
        }

    }

    fun addData(data: List<T>? , position:Int? = null) {
        if (!data.isNullOrEmpty()) { with(LinkedList(mData)){ position? .let {val startPosition = when {
                        it < 0 -> 0
                        it >= size -> size
                        else -> it
                    }
                    addAll(startPosition, data)
                }?: addAll(data)
                setData(this)}}}protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItem == newItem
    }

    protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }

    fun getData(a): List<T> {
        return mData
    }

    fun getItem(position: Int): T {
        return mData[position]
    }

    fun getActualPosition(data: T): Int {
        return mData.indexOf(data)}override fun getItemCount(a): Int {
        return mData.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> {
        returnWith (getViewBinding < VB > (LayoutInflater. The from (the parent. The context), the parent,1)) {
            setListener()
            BindViewHolder(this)}}override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) {
        with(holder.binding){
           onBindViewHolder(getItem(position), position)
           executePendingBindings()
        }
    }

    open fun VB.setListener(a) {}

    abstract fun VB.onBindViewHolder(bean: T, position: Int)

    class BindViewHolder<M : ViewDataBinding>(var binding: M) :
            RecyclerView.ViewHolder(binding.root)
}
Copy the code

We will ignore the BaseAdapter definition for the moment, and now we will modify the HomeAdapter to become:

class HomeAdapter : BaseAdapter<String, ItemHomeBinding>() {

    override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
       tv.text = bean
    }
}
Copy the code

When we use it in an Activity:

class MainActivity : BaseActivity<ActivityMainBinding>() {
    lateinit var homeAdapter:HomeAdapter
    override fun ActivityMainBinding.initBinding(a) {
        homeAdapter = HomeAdapter()
        with(recyclerView){
            layoutManager = LinearLayoutManager(this@MainActivity).apply {
                orientation = RecyclerView.VERTICAL
            }
            adapter = homeAdapter
        }
        homeAdapter.setData(listOf("a"."b"."c"."d"."e"."f"))}}Copy the code

Now the code in the Adapter has become super concise, I believe you can write 100 more Adapters without fear now.

Now let’s disassemble the BaseAdapter step by step. We see that BaseAdapter needs to pass in two generics, T is the data type of the entity we want to display, and VB is our layout binding ViewDataBinding.

abstract class BaseAdapter<T, VB : ViewDataBinding> 
    : RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() {
    / /...
}
Copy the code

As you can see below, we handle the initialization of the Item layout by implementing onCreateViewHolder in BaseAdapter. When we call getViewBinding, position is passed 1, which corresponds to the order in which VB is located:

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> {
        returnWith (getViewBinding < VB > (LayoutInflater. The from (the parent. The context), the parent,1)) {
            setListener()
            BindViewHolder(this)}}Copy the code

We also create an inner class, BindViewHolder, to create a unified ViewHolder.

    class BindViewHolder<M : ViewDataBinding>(var binding: M) :
            RecyclerView.ViewHolder(binding.root)
Copy the code

We call the setListener method of an empty implementation while initializing the layout. Why do we define open here rather than abstract? Because not every Adapter needs to set a listening event for Item, we treat the setListener as an optional Item.

    open fun VB.setListener(a) {}
Copy the code

After initialization, we need to perform layout binding, but the binding needs to be handled differently for different Adapter interfaces. Therefore, while implementing onBindViewHolder, We hand off our binding handling to subclasses by calling the internally created abstract method vb. onBindViewHolder.

    override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) {
        with(holder.binding){
           onBindViewHolder(getItem(position), position)
           executePendingBindings()
        }
    }
Copy the code

The Vb. onBindViewHolder parameter is also converted to the actual data bean and the corresponding position.

 abstract fun VB.onBindViewHolder(bean: T, position: Int)
Copy the code

So far in BaseAdapter we have dealt with:

  1. unifiedAdapterInitialization of the.
  2. To simplify theonBindViewHolderThe use of.
  3. You have to create it again every time you remove itViewHolder.
  4. Unify our SettingsItemThe listening event mode of

Now let’s look at how to unify the Adapter data refresh. You can see that we have created a private data set mData in BaseAdapter that holds the data types of the generic T that we want to display.

private var mData: List<T> = mutableListOf()
Copy the code

At the same time we have added a setData method in which we use DiffUtil to flush our data for comparison. If you are not familiar with DiffUtil, look up its method.

    fun setData(data: List<T>? {
        data? .let {val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(a): Int {
                    return mData.size
                }

                override fun getNewListSize(a): Int {
                    return it.size
                }

                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseAdapter.areItemsTheSame(oldData, newData)
                }

                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
                }
            })
            mData = data
            result.dispatchUpdatesTo(this)}? : let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size)
        }
    }
Copy the code

The DiffUtil.Callback method uses areItemsTheSame and AreContentsthesame to compare data. Is, in fact, we defined in BaseAdapter 2 open way areItemContentsTheSame, areItemsTheSame.

    protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItem == newItem
    }

    protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }
Copy the code

Why do we define it this way? Although both methods are implemented in BaseAdapter, we don’t know if subclasses need to change the way they compare when implemented. For example, when I use areItemsTheSame, generic T, if the generic T is not a basic data type, I usually just compare the unique key in the generic T. Now suppose the generic T is a data entity class User:

    data class User(val id:Int.val name:String)
Copy the code

When we subclass areItemsTheSame, we can use it in our implementation’s apapter as follows:

    protected open fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }
Copy the code

The observant might notice that we define a getActualPosition method, why not call it getPosition. This is because, for convenience, we may save the position when onBindViewHolder or pass the position when setting the listener.

If we insert or subtract some data from the previous data, but because we use DiffUtil to refresh, because the data of the existing bean is unchanged, only the position is changed, Therefore, onBindViewHolder will not be executed. In this case, when we directly use position, the position will be incorrect or the boundary will be crossed. For example:

interface ItemClickListener<T> {
    fun onItemClick(view: View,position:Int.data: T){}
    fun onItemClick(view: View.data: T)
}
Copy the code

We define two methods in ItemClickListener, which we demonstrate using the onItemClick method with position:


      
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="bean"
            type="String" />

        <variable
            name="position"
            type="int" />

        <variable
            name="itemClickListener"
            type="com.carman.kotlin.coroutine.interf.ItemClickListener" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:onClick="@{(v)->itemClickListener.onItemClick(v,position,bean)}"
            android:textColor="@color/black"
            android:textSize="18sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

We do the Click binding in the XML, and then we listen for events and data Settings in HomeAdapter

class HomeAdapter(private val listener: ItemClickListener<String>) : BaseAdapter<String, ItemHomeBinding>() {

    override fun ItemHomeBinding.setListener(a) {
      itemClickListener = listener
    }

    override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
        this.bean = bean
        this.position = position
        tv.text = bean
    }
}
Copy the code

Next we view the log in MainActivity by setting the data twice before clicking

class MainActivity : BaseActivity<ActivityMainBinding>() {
    lateinit var homeAdapter:HomeAdapter
    override fun ActivityMainBinding.initBinding(a) {
        homeAdapter = HomeAdapter(itemClickListener)
        with(recyclerView){
            layoutManager = LinearLayoutManager(this@MainActivity).apply {
                orientation = RecyclerView.VERTICAL
            }
            adapter = homeAdapter
        }
        homeAdapter.setData(listOf("a"."b"."c"."d"."e"."f"))
        btn.setOnClickListener {
              Log.d("Refresh"."Second setData")
            homeAdapter.setData(listOf("c"."d"."e"."f"))}}private val itemClickListener = object : ItemClickListener<String> {
        override fun onItemClick(view: View, position: Int.data: String) {
            Log.d("onItemClick"."data:$data   position:$position")}}}Copy the code
D/onItemClick: data:a position:0 D/onItemClick: data:b position:1 D/onItemClick: data:c position:2 D/onItemClick: Data :d position:3 d /onItemClick: data:e position:4 d /onItemClick: data:f position:5 d/refresh: second setData d /onItemClick: data:c position:2 D/onItemClick: data:d position:3 D/onItemClick: data:e position:4 D/onItemClick: data:f position:5Copy the code

So we need to get the actual position using position, preferably via getActualPosition, and modify the log output in itemClickListener.

    private val itemClickListener = object : ItemClickListener<String> {
        override fun onItemClick(view: View, position: Int.data: String) {
            Log.d("onItemClick"."data:$data   position:${homeAdapter.getActualPosition(data)}")}}Copy the code

If we repeat the above operation, we can see that position is exactly where it is currently located.

D/onItemClick: data:c   position:0
D/onItemClick: data:d   position:1
D/onItemClick: data:e   position:2
D/onItemClick: data:f   position:3
Copy the code

So far, we have a general idea of the BaseAdapter

abstraction and how to use it.
,>

Note that, for the sake of a simple demonstration, we will assume that the Databinding binding is not done directly in XML. There is some complex logic that we simply cannot bind in XML.

Obviously our work does not end there, as our Adapter still has multiple layouts in common scenarios, so how do we deal with that?

It’s actually pretty easy to do. Since we are multi-layout, that means we need to expose some of the work in onCreateViewHolder to subclasses, so we need to make some changes to the BaseAdapter above. As usual:

abstract class BaseMultiTypeAdapter<T> : RecyclerView.Adapter<BaseMultiTypeAdapter.MultiTypeViewHolder>() {

    private var mData: List<T> = mutableListOf()

    fun setData(data: List<T>? {
        data? .let {val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(a): Int {
                    return mData.size
                }

                override fun getNewListSize(a): Int {
                    return it.size
                }

                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseMultiTypeAdapter.areItemsTheSame(oldData, newData)
                }

                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldData: T = mData[oldItemPosition]
                    val newData: T = it[newItemPosition]
                    return this@BaseMultiTypeAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
                }
            })
            mData = data
            result.dispatchUpdatesTo(this)}? : let { mData = mutableListOf() notifyItemRangeChanged(0, mData.size)
        }

    }

    fun addData(data: List<T>? , position:Int? = null) {
        if (!data.isNullOrEmpty()) { with(LinkedList(mData)) { position? .let {val startPosition = when {
                        it < 0 -> 0
                        it >= size -> size
                        else -> it
                    }
                    addAll(startPosition, data)}? : addAll(data)
                setData(this)}}}protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItem == newItem
    }

    protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
        return oldItem == newItem
    }

    fun getData(a): List<T> {
        return mData
    }

    fun getItem(position: Int): T {
        return mData[position]
    }

    fun getActualPosition(data: T): Int {
        return mData.indexOf(data)}override fun getItemCount(a): Int {
        return mData.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiTypeViewHolder {
        return MultiTypeViewHolder(onCreateMultiViewHolder(parent, viewType))
    }

    override fun onBindViewHolder(holder: MultiTypeViewHolder, position: Int) {
        holder.onBindViewHolder(holder, getItem(position), position)
        holder.binding.executePendingBindings()
    }

    abstract fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: T, position: Int)

    abstract fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding

    protected fun <VB :ViewDataBinding> loadLayout(vbClass: Class<VB>,parent: ViewGroup): VB {
        val inflate = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean: :class.java)
        return inflate.invoke(null, LayoutInflater.from(parent.context), parent, false) as VB
    }

    class MultiTypeViewHolder(var binding: ViewDataBinding) :
            RecyclerView.ViewHolder(binding.root)
}
Copy the code

As you can see from the code above, we did not define the generic VB :ViewDataBinding in the BaseMultiTypeAdapter, because we are multi-layout, it is obviously not appropriate to write all in the class definition, and we do not know how many layouts are required in the implementation.

So when we initialize the layout onCreateViewHolder we call an abstract onCreateMultiViewHolder method, which is implemented by our concrete business implementation class. At the same time, we modify onBindViewHolder to add a holder parameter for external use. Let’s start with the entity type

sealed class Person(open val id :Int.open val name:String)

data class Student(
        override val id:Int.override val name:String,
        val grade:String):Person(id, name)

data class Teacher(
        override val id:Int.override val name:String,
        val subject:String):Person(id, name)
Copy the code

And the Adapter business class we need to implement,

class SecondAdapter: BaseMultiTypeAdapter<Person>() {

    companion object{
        private const val ITEM_DEFAULT_TYPE = 0
        private const val ITEM_STUDENT_TYPE = 1
        private const val ITEM_TEACHER_TYPE = 2
    }

    override fun getItemViewType(position: Int): Int {
        return when(getItem(position)){
            is Student -> ITEM_STUDENT_TYPE
            is Teacher -> ITEM_TEACHER_TYPE
            else -> ITEM_DEFAULT_TYPE
        }
    }

    override fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding {
      return when(viewType){
          ITEM_STUDENT_TYPE -> loadLayout(ItemStudentBinding::class.java,parent)
          ITEM_TEACHER_TYPE ->  loadLayout(ItemTeacherBinding::class.java,parent)
           else ->  loadLayout(ItemPersionBinding::class.java,parent)
       }
    }

    override fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: Person, position: Int) {
        when(holder.binding){
            is ItemStudentBinding ->{
                Log.d("ItemStudentBinding"."item : $item   position : $position")}is ItemTeacherBinding ->{
                Log.d("ItemTeacherBinding"."item : $item   position : $position")}}}}Copy the code
class MainActivity : BaseActivity<ActivityMainBinding>() {
    override fun ActivityMainBinding.initBinding(a) {
        val secondAdapter = SecondAdapter()
                with(recyclerView){
            layoutManager = LinearLayoutManager(this@MainActivity).apply {
                orientation = RecyclerView.VERTICAL
            }
            adapter = secondAdapter
        }
        secondAdapter.setData(
                listOf(
                        Teacher(1."Person"."Chinese"),
                        Student(2."Person"."First grade"),
                        Teacher(3."Person"."Mathematics")))}Copy the code

Run it to see what we want:

D/ItemTeacherBinding: item : Teacher(id=1, name=Person, subject= language) position:0
D/ItemStudentBinding: item : Student(id=2, name=Person, grade= grade)1
D/ItemTeacherBinding: item : Teacher(id=3, name=Person, subject= mathematics) position:2
Copy the code

After the above processing, we reduced a lot of code when creating Activiy, Fragment, and Adapter. At the same time, it also saves the time of repeating the garbage code, and at least improves your work efficiency by at least 10 percentage points, which makes you feel more handsome.

Dialog, PopWindow, dynamically initialized View. Then what are you waiting for? Just make it happen. After all, it is better to teach a man how to fish than to give him a fish.

Need source code to see here: Demo source

Originality is not easy. If you like this article, you can click “like”.

Related articles

This article is an extension of the kotlin coroutine, which can be read at the following link if you are interested

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines

Extend the series

  • A ViewModel wrapper for everyday use