How to access object in viewModel from outside activity or fragment class in Kotlin?

Viewed 1214

I have a app in which I'm trying go implement the MVVM pattern for the first time. I have a simple fragment with the corresponding viewModel and a seperate class that deals with the swipeToDelete from the recyclerView which is in the fragment.

The viewModel looks like this:

import androidx.lifecycle.ViewModel

class ListViewModel : ViewModel() {

var keedList: ArrayList<Keed> = ArrayList()
}

And the SwipeToDelete class like this:

import android.util.Log
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class SwipeToDelete : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {

// since the feature is not used, simply return "false"
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: 
RecyclerView.ViewHolder) = false

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
    val position: Int = viewHolder.adapterPosition
    keedList.removeAt(position)              // this is not working, "keedList" is red.
    viewModel.keedList.removeAt(position)    // this is not working either... "viewModel" is red.
    myAdapter.notifyItemRemoved(position)
    }
}

the other files should be irrelevant I guess.

Now the problem ist, that I can't remove my swiped off item in the "onSwiped" function since it won't recognize my "keedList" or the "viewModel" in the function because the SwipeToDelete class is neighter fragment or activity (I tried both cases...).

Is there a general flaw on how I'm designing it? How could that be solved?

Thanks for your help.

And here is my code in the fragment before using on of the two approaches:

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_list.*
import marcelfuchs.example.org.keed.databinding.FragmentListBinding

lateinit var myAdapter: RecyclerAdapter

class MainListFragment : Fragment() {

    private val viewModel: ListViewModel by activityViewModels()

    private var _binding: FragmentListBinding? = null

    // This property is only valid between onCreateView and onDestroyView.
    private val binding get() = _binding!!


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_list, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        myAdapter = RecyclerAdapter(viewModel.keedList)
        rv_killsDeaths.layoutManager = LinearLayoutManager(MainActivity())
        rv_killsDeaths.adapter = myAdapter

        binding.fab.setOnClickListener {
            findNavController().navigate(R.id.action_listFragment_to_enterItemsFragment)
        }

        // close the softKeyboard as it keeps on opening when returning from NewItemFragment
        val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(requireView().windowToken, 0)

        val itemTouchHelper = ItemTouchHelper(SwipeToDelete())
        itemTouchHelper.attachToRecyclerView(rv_killsDeaths)

        super.onViewCreated(view, savedInstanceState)
    }

    //Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's onDestroyView() method.
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
1 Answers

You could pass a listener to the SwipeToDelete class and invoke it whenever an item in the RecyclerView is swiped.

class SwipeToDelete(private val deletionListener: (Int) -> Unit) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
    
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val position: Int = viewHolder.adapterPosition
        deletionListener.invoke(position)   
    }
}

In your fragment, you can remove that item from your list

val swipeToDeleteCallback = SwipeToDelete { position ->
    viewModel.keedList.removeAt(position)
    myAdapter.notifyItemRemoved(position)
}

val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchHelper.attachToRecyclerView(rv_killsDeaths)

Or, you could create an object within the fragment instead of a separate class (Makes sense if it is not being reused in other fragments/activities). Like so:

val swipeToDeleteCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val position: Int = viewHolder.adapterPosition
        viewModel.keedList.removeAt(position)
        myAdapter.notifyItemRemoved(position)   
    }
}

val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchHelper.attachToRecyclerView(rv_killsDeaths)
Related