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