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