Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding ViewModelStore

Is it possible to provide once own implementation of a ViewModelStore for ViewModelProviders to use instead of the default one?

More precisely, I'm interested in adding fun clear(vm: ViewModel) (or using an index or something similar) functionality to the ViewModelStore so that I can clear a single view model of my choice, not just use the built in ViewModelStore#clear:

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.onCleared();
    }
    mMap.clear();
}

which clears all view models.

like image 792
Algar Avatar asked Apr 09 '18 18:04

Algar


People also ask

What is ViewModelStore?

A ViewModelStore can be considered as a container that stores the ViewModels in a HashMap . Where the key is string value and value is the ViewModel being saved( ViewModelProvider uses a concatenation of the string_key + ViewModel class canonical name). A ViewModelStoreOwner is merely an interface.

Is ViewModel destroyed when activity destroy?

It is because of the Android system destroys the activity and recreates it from scratch, and as the activity is recreated, the old resources and instances of various classes(ViewModelProvider in our case) are first destroyed on calling onDestroy() method and later recreated in onCreate() .

How does a ViewModel survive?

ViewModel is a separate class which has an instance of it in the activity and has no reference for view in it. So even though activity onDestroy() and onCreate() happens on configuration change, the instance of ViewModel doesn't get destroyed or garbage collected.

How does ViewModel retain configuration changes?

Using ViewModel to Store UI State. The Android team introduced ViewModel and LiveData classes to help save state during configuration changes. A ViewModel stores and manages UI-related data in a lifecycle-conscious manner. Simply put, it allows data to survive configuration changes.


1 Answers

First, I think you should not consider doing that, because that's an implementation detail of Architecture Components library. Most possibly you should come up with a better solution as a result of adapting your use-case to match guidelines/contracts exposed by ViewModels API.

Nevertheless, let's examine possibilities of doing that.

Here's the code, that we should use in order to obtain a ViewModel implementation:

val viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

What will this code result in, is that it will create an instance of HolderFragment, which is a retained fragment, and will attach it to this's fragment manager (might be either FragmentActivity's fragment manager or Fragment's child fragment manager).

This HolderFragment will be added with a HolderFragment.HOLDER_TAG, thus we are able to get an instance of this fragment from the fragment manager.

val holderFragment = supportFragmentManager.findFragmentByTag("android.arch.lifecycle.state.StateProviderHolderFragment") as HolderFragment

It's the HolderFragment, that creates an instance of ViewModelStore and keeps that instance as a private field. There exists a getter for that field, but there does not exist a setter, which means, that the only way to "substitute" this object is by using reflection.

But before doing that, let's try to write a custom implementation of ViewModelStore class:

class MyViewModelStore : ViewModelStore() {

  private val mMap = HashMap<String, ViewModel>()

  internal fun put(key: String, viewModel: ViewModel) {
    val oldViewModel = mMap.put(key, viewModel)
    oldViewModel?.onCleared() // COMPILATION ERROR -> Cannot access 'onCleared': it is protected/*protected and package*/ in 'ViewModel'
  }

  internal operator fun get(key: String): ViewModel? {
    return mMap[key]
  }

  override fun clear() {
    for (vm in mMap.values) {
      vm.onCleared() // COMPILATION ERROR -> Cannot access 'onCleared': it is protected/*protected and package*/ in 'ViewModel'
    }
    mMap.clear()
  }

}

Unfortunately, we cannot do that, because ViewModel#onCleared() has a protected package access, which makes impossible for us call it outside of the android.arch.lifecycle package. Again, we can use reflection to do that (but how good is that?).

Despite being not advised (by me), seems like that's also not achievable to do (without using reflection).

like image 131
azizbekian Avatar answered Oct 07 '22 15:10

azizbekian