androidandroid-recyclerviewandroid-viewmodelonitemclicklistenerandroid-mvvm

Accessing ViewModel functions from within a RecyclerView adapter


I am new to Android development and working through some basic Android apps to learn more. I want to learn MVMM architecture best practices as early as possible and am curious how to best solve the following task: In a simple list app, my ViewModel holds list data and also functions for adding and deleting the data (this I learned from Android Developer tutorials).

A RecyclerView displays the list data in a Fragment. I am using the RecyclerView adapter as a 'bridge' between the UI list item a user interacts with and the appropriate ViewModel function call. Initially, I implemented this using an interface but this would not allow me to pass a reference to the ViewModel as a constructor parameter so now I am using an inner class as described in many SO posts regarding setting ClickListeners in a RecyclerView adapter:

class MainAdapter(
    val viewModel: ListViewModel, // reference to entire ViewModel just to access functions therein
    private var list: LiveData<MutableList<String>>, // stored in ViewModel
    private val listener: OnItemClickListener // defined in inner class below
) :
    RecyclerView.Adapter<MainAdapter.ViewHolder>() {

    inner class OnItemClickListener(viewModel: ListViewModel) { 
        fun onClick(mainItem: String) {
            // (TODO) navigate to DetailListFragment and display appropriate list
        }

        fun onLongClick(mainItem: String) {
            // contact viewModel to delete this item
            viewModel.deleteItemMainList(mainItem)
        }
    }

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.itemView.list_item_text.text = list.value?.elementAt(position)
        holder.itemView.setOnClickListener(View.OnClickListener {
            listener.onClick(list.value?.elementAt(position)!!)
        })
        holder.itemView.setOnClickListener(View.OnClickListener {
            listener.onLongClick(list.value?.elementAt(position)!!)
        })
    }

    override fun getItemCount(): Int {
        return list.value?.size ?: 0
    }

}

Is there a better solution than passing a reference to my entire ViewModel only to access the functions therein?

If this is the most appropriate solution, I am encountering an error when initializing the adapter within the fragment class:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...

        // Error: 
        // Constructor of inner class OnItemClickListener can be called only with receiver of containing class: 
        adapter = MainAdapter(viewModel ,viewModel.mainList, MainAdapter.OnItemClickListener(viewModel) )
        
        main_list_view.adapter = adapter
        main_list_view.layoutManager = LinearLayoutManager(requireContext())
        recyclerView = binding.mainListView
    }

My ViewModel containing data and functions:

class ListViewModel : ViewModel() {
    private val _mainList = MutableLiveData<MutableList<String>>()
    val mainList: LiveData<MutableList<String>> = _mainList

    init {
        _mainList.value = arrayListOf()
        _detailList.value = arrayListOf(arrayListOf())
    }

    fun addItemMainList(item: String) {
        if (_mainList.value?.contains(item) == false) {
            _mainList.value?.add(item)
            _mainList.value = _mainList.value
            }
    }

    fun deleteItemMainList(item: String) {
        _mainList.value?.remove(item)
        _mainList.value = _mainList.value
    }
}

Lastly, i realize storing list data this way is not persistent and Room is the appropriate way to persist data, but first I want to understand how to correctly connect UI events and my ViewModel using MVMM best practices.

Sample of references explored thus far:

How to acces shared viewModel in my recyclerAdapter

RecyclerView Item Click Listener the Right Way

https://discuss.kotlinlang.org/t/kotlin-constructor-of-inner-class-nested-can-be-called-only-with-receiver-of-containing-class/7700


Solution

  • StackOverflow answer on recycler view and Viewmodels. This comment from one of the resources you shared answers your question. Just pass only what is necessary ( for example a list ) then create interfaces in your recyclerView to get called in the calling activity or Fragment . Then access your Viewmodel from there.

    Is it okay to pass ViewModel to recyclerView?.. I mean the recycler view will be tied to the lifecycle of the fragment or activity you call it from recycler view so it's okay but not a good practice