Won't notifyDataSetChanged() launch init{ } in RecyclerView.ViewHolder in Android Studio?

Viewed 239

I'm learning RecyclerView with ListAdapter.

1: I find setControl() in init{ } isn't be launched after I run notifyDataSetChanged() , why?

2: What code should I place it in init{ }? What code should I place it in fun bind(aMVoice: MVoice{ } ?

Code

class VoiceAdapters (private val aHomeViewModel: HomeViewModel):
        ListAdapter<MVoice, VoiceAdapters.VoiceViewHolder>(MVoiceDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VoiceViewHolder {
        return VoiceViewHolder(
            LayoutVoiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: VoiceViewHolder, position: Int) {
        val aMVoice = getItem(position)
        holder.bind(aMVoice)
    }

    inner class VoiceViewHolder (private val binding: LayoutVoiceItemBinding):
          RecyclerView.ViewHolder(binding.root) {

       
        init {
            setControl()
        }

        fun bind(aMVoice: MVoice) {
            binding.amVoice = aMVoice                
            binding.executePendingBindings()
        }

       
        fun setControl(){    
           binding.aHomeViewModel = aHomeViewModel    
           binding.chSelect.setOnCheckedChangeListener{ _, isChecked ->
                binding.amVoice?.let {
                   ...
                }
            }
             
            ...
         }

    }
}

Added Content:

To ADM: Thank you very much!

A: Why isn't it a good idea to passing a ViewModel to adapter ?

B: How can I use interface instead ? Could you show me some sample code?

BTW, the following item layout of RecyclerView need to use ViewModel aHomeViewModel to control whether the CheckBox chSelect is shown or not. I will set the value of aHomeViewModel.displayCheckBox in a fragment.

layout_voice_item.xml

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

<data>
    <import type="android.view.View" />

    <variable name="aMVoice"
        type="info.dodata.voicerecorder.model.MVoice"  />
   
    <variable name="aHomeViewModel"
        type="info.dodata.voicerecorder.viewcontrol.HomeViewModel" />
</data>


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"   
    android:orientation="horizontal">

    <CheckBox
        android:id="@+id/chSelect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="@{aHomeViewModel.displayCheckBox? View.VISIBLE: View.GONE}"
        android:text="" />

    <TextView
       android:id="@+id/voiceID"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@{Integer.toString(aMVoice.id)}" />

</LinearLayout>
</layout>
2 Answers

RecyclerView reuse same ViewHolder multiple times that's why constructor does not get called . So any binding Stuff that should be done for all items should written inside onBindViewHolder.

On calling notifyDataSetChanged onBindViewHolder will get called for positions which are visible on screen and the same ViewHolder will be reused (Well it depends). But the thing here is a new ViewHolder will not be created each time so you can not us constructor for such operation.

What code should I place it in init{ }? What code should I place it in fun bind(aMVoice: MVoice{ } ?

Well inside init you can find views and set the action listeners. Inside bind you do the stuff for each item i.e setting data to the views.

On other hand you should not be passing a ViewModel to adapter that's not a good idea, Use an interface instead.

Why you don't pass ViewModel to Adapter Well ultimately ViewModel is just a class so you can pass it and it won't give any error . The reasons i can think of right now to not do it are follows :-

  1. By Passing ViewModel yo are tightly coupling the adapter to a single ViewModel i.e in turns Activity or a fragment. Now you can not reuse this Adapter any any other place

  2. Also the whole point of having a ViewModel is to observe the data stream which don't usually happen inside Adapter.

Remember one thing ViewModel(LiveData) is not a replacement of Callback interface. So you should be using callback interface here Since you do not need a lifecycle component here.

So instead of passing ViewModel direactly pass the dataset to the adapter And if you need to notify the Activity or Fragment on the actions (click, long click) use a Interface.

init will be called once when the ViewHolder is created. Thats why RecyclerView has the word recycle in it. It will reuse its objects. Therefor init will only be called once in the Lifecycle of ViewHolder. bind will be called, when the content is visible (or near visible). Call setControl in bind.

Related