Preface: this article is based on BaseRecyclerViewAdapterHelper – 2.9.34 version carries on the analysis, this is my first technical article published, if write bad please include me hard to understand, thank you! The article is long, but not very complicated, easy to understand, if you have any questions in the comments section; If you see not bottom go to, can be directly the final summary and BaseRecyclerViewAdapterHelper ViewBinding final assembly code This article mainly want to readers in this paper, the two problems and solutions:

  • BaseViewHolder based on BRVAH when customizingViewholder occur class conversion exception problems and ideas
  • BRVAH and ViewBinding encapsulation make it easier to use controls in adapters

PS: BaseRecyclerViewAdapterHelper I BRVAH for short


1. Simple BRVAH combined with ViewBinding

The code for simple encapsulation is as follows. The core is to add a ViewBinding attribute to the ViewHolder: BasebindingAdapter.kt

abstract class BaseBindingAdapter<VB: ViewBinding, T>(data: List<T>? = null):
        BaseQuickAdapter<T, VBViewHolder<VB>>(0.data) {

    override fun convert(holder: VBViewHolder<VB>, item: T) {
        convertPlus(holder.binding, item)
    }

    abstract fun convertPlus(binding: VB, item: T)

    abstract fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup): VB

    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VBViewHolder<VB> {
        val binding = createViewBinding(LayoutInflater.from(parent.context), parent)
        return VBViewHolder(binding, binding.root)
    }
}
Copy the code

VBViewHolder.kt

class VBViewHolder<VB: ViewBinding>(val binding: VB, view: View): BaseViewHolder(view)
Copy the code

MainActivity2.kt

class MainActivity2 : AppCompatActivity() {

    private val mBinding by lazy {
        ActivityMain2Binding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        val list = listOf("If we had never met."."Where would you be?"."Live every second."."Dying every second."."If we had never met."."Where would you be?"."Live every second."."Dying every second.")
        mBinding.contentRv.apply {
            layoutManager = LinearLayoutManager(this@MainActivity2)
            adapter = InnerAdapter()
        }
    }

    class InnerAdapter(list: List<String> = ArrayList()): BaseBindingAdapter<ItemItemBinding, String>(list) {
        
        override fun convertPlus(binding: ItemItemBinding, item: String) {
            binding.desTv.text = item
        }

        override fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup) =
            ItemItemBinding.inflate(inflater, parent, false)}}Copy the code

This packaging looks no problem, write an interface to add a RecyclerView test can also normal display operation

2. Set up an emptyView and find a BRVAH error in Logcat

Add the following code:

mBinding.contentRv.apply {
            layoutManager = LinearLayoutManager(this@MainActivity2)
            adapter = InnerAdapter().also {
            // Add an empty layout
                it.setEmptyView(R.layout.item_item, this)}}Copy the code

Re-run the demo and it works fine, no crashes, but there is an error in Logcat:Meaning there is no custom in myVBViewHolderFound a constructor with arguments of type View, indeed I customVBViewHolderConstructor arguments View and ViewBinding for BRVAHReflection needs to find a constructor that has only a View argument, and you can see that this place istry-catchIf not, we catch the exception and return it to null. We see that this is where the error message was caught and printed, which fosters the crash that follows

3. Rewrite the RecyclerView onBindViewHolder method.

Rewrite onBindViewHolder in RecyclerView adapter class code as follows:

class InnerAdapter(list: List<String> = ArrayList()): BaseBindingAdapter<ItemItemBinding, String>(list) {

        override fun onBindViewHolder(holder: VBViewHolder<ItemItemBinding>, position: Int, payloads: MutableList<Any>) {
            super.onBindViewHolder(holder, position, payloads)
        }
    }
Copy the code

When we call the parent class’s method and re-run the demo, we find that the application crashes. The crash message is as follows:Say that IBaseViewHolderConverted toVBViewHolderFailed, logically I am in the custom adapterBaseBindingAdapterOverride method inonCreateDefViewHolderReturns my customVBViewHolder, so logically this conversion failure should not happen. I wonder if I added an emptyView in step 2,I got rid of this and rewrote the app, and it worked fine without crashing

4. Analyze the cause of collapse with BRVAH

According to the previous analysis, the phenomenon of the problem is as follows:

The onBindViewHolder application crashed when emptyView was removed or the onBindViewHolder method was removed

Let’s analyze this phenomenon according to the source code:

  • BRAVH did not call me to override the ViewHolder created for emptyViewonCreateDefViewHolderThe ViewHolder corresponding to emptyView is not customVBViewHolderBRAVH provided itBaseViewHolder

You can see:

  • If you add emptyView, footView, headerView, it will passcreateBaseViewHolderThe ViewHolder () method creates a ViewHolder
  • If the ViewHolder is created using the data source we passed in, our custom methods will be usedonCreateDefViewHolderreturnVBViewHolder
  • createBaseViewHolder()Perform logical analysis



You can see:

  • Class variablezthroughgetInstancedGenerickClassMethod returns what we passedBaseQuickAdapter<T, K entends BaseViewHolder>The class object of VBViewHolder passed in (when we inherit from BaseQuickAdapter we need to pass in a generic type whose upper bound is BaseViewHolder, which is our custom VBViewHolder)
  • throughcreateGenericKInstance()Method to try to create a ViewHolder object, and then examine the execution flow of the method
  • createGenericKInstance()Perform logical analysis

You can see:

  • This method, which we briefly examined earlier, takes a constructor with a View argument via reflection, howeverVBViewHolderIs not provided in logcat, so an error occurs that was not found in the previous logcat method
  • And then eventually the result is returned to NULL, and we go back to the previous methodcreateBaseViewHolder()In the execution logic of

The last code returned by createBaseViewHolder() will be pasted again:



Through our work oncreateGenericKInstance()Analysis of the variableskThe value of k will eventually be assigned to null, the logic in the red box is that when the variable k is null we will create oneBaseViewHolderObject strongly casts to the type of the generic K,Here lies the root of the problemFirst, we know:

Generics are erased at compile time. If there is no upper bound, the erased type is Object, if there is an upper bound, the erased type is upper bound, and the erased type is BaseViewHolder, So the type of BaseViewHolder that you force is going to be BaseViewHolder, which is this right here which is the Holder type that emptyView corresponds to

  • onBindViewHolder()Methods to analyze

See how the parent RecyclerView method looks like:

And then when we rewrite it in the RecyclerView adapteronBindViewHolderHere’s what it looks like:

You can see:

  • RecyclerView originalonBindViewHolderThe first parameter passed to the method is of type VH, which is passed through the following



So this VH type is just after the generic erasureBaseViewHolderType, so when we call nativeonBindViewHolderEmptyView (holder)

  • If we override it in a custom adapteronBindViewHolderMethod,The first argument type is VBViewHolder, but the emptyView we passed is BaseViewHolder, so the application crashes

5. Solutions to problems

From our analysis above, we can see that the key point that went wrong was that the ViewHolder created for emptyView was BaseViewHolder rather than VBViewHolder. CreateGenericKInstance () failed because our custom ViewHolder does not have a constructor that contains only View as a type argument. So we can solve this problem by creating a constructor in our custom ViewHolder that contains only the View parameter type

The code is as follows: vbViewholder.kt

class VBViewHolder<VB: ViewBinding> @JvmOverloads constructor(view: View, val binding: VB? = null): BaseViewHolder(view)
Copy the code

BaseBindingAdapter.kt

abstract class BaseBindingAdapter<VB: ViewBinding, T>(data: List<T>? = null):
        BaseQuickAdapter<T, VBViewHolder<VB>>(0.data) {

    override fun convert(holder: VBViewHolder<VB>, item: T){ convertPlus(holder.binding!! , item) }abstract fun convertPlus(binding: VB, item: T)

    abstract fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup): VB

    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VBViewHolder<VB> {
        val binding = createViewBinding(LayoutInflater.from(parent.context), parent)
        return VBViewHolder(binding.root, binding)
    }
}
Copy the code

6. Summary

If we define a ViewHolder class based on BRVAH's BaseViewHolder that does not provide a constructor that contains only one View constructor parameter, then when we set emptyView, footView, headerView, BRVAH creates a BaseViewHolder for these views, and a cast error occurs when we override onBindViewholder() to indicate that the Viewholder type is our custom Holder type

So when we customize a ViewHolder class for BRVAH's BaseViewHolder, the class must provide a constructor that contains only one View constructor parameter