Kotlin: How to pass data from SharedViewModel to Adapter

Issue

I’m working on an app with a bottom navigation bar and struggle when trying to access a sharedViewModel’s data across fragments and adapters. My code is already quite full so I’ll be trying to list it up in short here. If needed, I can of course supply the whole code.

Because of different fragments having to access the same data, I created a sharedViewModel class DeactivatedElementsViewModel (that’s where things start to get complicated). In an exemplary fragment there is the fragment class DeactivatedJumpElementsFragment : Fragment(R.layout.deactivated_jump_elements_fragment) accompanied by the adapter myAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() which equips a list inside the fragment with buttons and strings.

In the fragment class I begin with lateinit var sharedViewModel: DeactivatedElementsViewModel and lateinit var elementeAusStand: Map<String,Array<Any>>before successfully working with the sharedViewModel in e.g. onViewCreated(inside the fragment) like so elementsFromStand= sharedViewModel.elementsFromStand.

Now I struggle with accessing data from the sharedViewModel inside the adapter‘s function onBindViewHolder.

I tried different approaches like the following:

  • directly loading elementsFromStand inside onBindViewHolderby implementing the sharedViewModel there which leads to the error "Can’t access ViewModels from detached fragment"
  • loading elementsFromStand inside onBindViewHolder via the sharedViewModel declared in the fragment’s class like val elementsFromStand= DeactivatedJumpElementsFragment().elementsFromStand which led to the error of the sharedViewModel being called before initialized. I tried to intercept this by
    if (DeactivatedJumpElementsFragment()::elementsFromStand.isInitialized){val elementsFromStand = DeactivatedJumpElementsFragment().elementsFromStand} which simply
    won’t ever be true/ run in runtime although the variable
    elementsFromStandis indeed initialized in onViewCreated()
  • using nested functions, trying to call the variable elementsFromStand via a function getSharedViewModelVariable from onCreate() but I fail to successfully retrieve it this way.

That’s where I need help. How do I (easily?) access the view model’s variables from my adapter?
Thanks for reading and for any hint!

Solution

The problem you are facing is that you are trying to access the sharedViewModel from the adapter, which is not a lifecycle owner and does not have a reference to the fragment’s view. A possible solution is to pass the sharedViewModel as a parameter to the adapter’s constructor, and then use it in the onBindViewHolder method. For example:

// In your fragment class, initialize the sharedViewModel and the adapter
private val sharedViewModel: DeactivatedElementsViewModel by activityViewModels()
private lateinit var adapter: MyAdapter

// In your onViewCreated method, create the adapter instance and pass the sharedViewModel
adapter = MyAdapter(sharedViewModel)
recyclerView.adapter = adapter

// In your adapter class, accept the sharedViewModel as a parameter and store it as a property
class MyAdapter(private val sharedViewModel: DeactivatedElementsViewModel) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    // In your onBindViewHolder method, use the sharedViewModel to access the data
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val elementsFromStand = sharedViewModel.elementsFromStand
        // Do something with the elementsFromStand
    }
}

This way, you can access the sharedViewModel’s data from the adapter without creating a new instance of the fragment or using detached views. However, you should also be careful about updating the data in the sharedViewModel, as it might affect other fragments that are using it. You might want to use LiveData or other observable patterns to handle data changes and notify the adapter accordingly.

Explanation:

The reason why you can’t access the sharedViewModel from the adapter directly is that the adapter is not a lifecycle owner, which means it does not have a lifecycle that is tied to the fragment’s view. The sharedViewModel is scoped to the activity’s lifecycle, which means it can be shared by multiple fragments, but it also requires a lifecycle owner to access it. The fragment is a lifecycle owner, and it can access the sharedViewModel by using the activityViewModels() delegate, which provides the same instance of the sharedViewModel to all the fragments in the same activity. However, the adapter is not a lifecycle owner, and it does not have a reference to the fragment’s view, so it can’t use the activityViewModels() delegate or the viewModels() delegate (which provides a fragment-specific instance of the viewModel).

One way to solve this problem is to pass the sharedViewModel as a parameter to the adapter’s constructor, and then store it as a property in the adapter class. This way, the adapter can access the sharedViewModel’s data from the onBindViewHolder method, which is called when the adapter binds the data to the view holder. This approach is simple and straightforward, but it also has some drawbacks. For example, if the data in the sharedViewModel changes, the adapter might not be aware of it, and it might display outdated or inconsistent data. To avoid this, you might want to use LiveData or other observable patterns to observe the data changes in the sharedViewModel and notify the adapter to update the view accordingly. You might also want to consider the impact of updating the data in the sharedViewModel from the adapter, as it might affect other fragments that are using the same sharedViewModel. You might want to use some logic or events to coordinate the data updates and avoid conflicts or errors.

Answered By – PaulvdBoor

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published