📜 ⬆️ ⬇️

Retain inside and outside ViewModel

image

At some point, I noticed periodic conversations about how the ViewModel actually works from google architectural components. Realizing that I do not fully understand the Internet myself, I was surprised to find that there are an incredible number of similar articles on how to prepare a ViewModel, be friends with LiveData, add dependencies to it through Dagger, link with RxJava and other titles of varying degrees of utility, however, there is almost nothing about what is going on inside. So I'll try to close the gap myself.

Attention


TL; DR If you feel sorry for the time - shake down to the conclusion, you will lose little.

So the first thing you can pay attention to is that there are 2 different packages of architectural components with ViewModel, namely:

1) Old android.arch.lifecycle
2) New androidx.lifecycle

Spoiler : there is no particular difference between them.

All the work lies behind the challenge:

ViewModelProviders.of(activity).get(MyViewModel::class.java) 

Let's start with the method of

  public static ViewModelProvider of(@NonNull FragmentActivity activity) { return of(activity, null); } public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) { Application application = checkApplication(activity); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(ViewModelStores.of(activity), factory); } 

checkApplication simply checks for null, and AndroidViewModelFactory is just a thread-safe singleton that stores Application. So they are of no particular interest, the most interesting in the ViewModelStores.of method:

  public static ViewModelStore of(@NonNull FragmentActivity activity) { if (activity instanceof ViewModelStoreOwner) { return ((ViewModelStoreOwner) activity).getViewModelStore(); } return holderFragmentFor(activity).getViewModelStore(); } 

At first glance, it looks rather strange - why is it even checking for the presence of the FragmentActivity ViewModelStoreOwner interface if it already implements it ? - It wasn’t always like this - until February 2018, when Support Library 27.1.0 was released , FragmentActivity never implemented ViewModelStoreOwner. At the same time, ViewModel worked quite well for itself.

So let's start with the old case - the holderFragmentFor method was launched :

  public static HolderFragment holderFragmentFor(FragmentActivity activity) { return sHolderFragmentManager.holderFragmentFor(activity); } 

Then just get or create a new holder fragment:

  HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(fm); if (holder != null) { return holder; } holder = mNotCommittedActivityHolders.get(activity); if (holder != null) { return holder; } if (!mActivityCallbacksIsAdded) { mActivityCallbacksIsAdded = true; activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); } holder = createHolderFragment(fm); mNotCommittedActivityHolders.put(activity, holder); return holder; } 

Well, HolderFragment itself is of course retained

  public HolderFragment() { setRetainInstance(true); } 

Actually, the ViewModelStere object is stored in it, which in turn holds the ViewModel pack in itself:

  public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } public final void clear() { for (ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); } } 

Go back to the case when the support library version is 27.1.0 and higher. FragmentActivity already implements the ViewModelStoreOwner interface, that is, the implementation of the only getViewModelStore method:

  public ViewModelStore getViewModelStore() { if (this.getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call."); } else { if (this.mViewModelStore == null) { FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance(); if (nc != null) { this.mViewModelStore = nc.viewModelStore; } if (this.mViewModelStore == null) { this.mViewModelStore = new ViewModelStore(); } } return this.mViewModelStore; } } 

Here I will simplify a little - NonConfigurationInstances is an object with what should not depend on the configuration (obviously from the name), which lies in the Activity and sweeps inside the ActivityClientRecord through ActivityThread during the re-creation between onStop and onDestroy

In general, it looks quite funny - instead of a live hacking with ViewModel transfer inside the retain fragment, the developers made a clever move - they used exactly the same mechanism, but got rid of the need to create an extra fragment each time.

Activity has always had an interesting onRetainNonConfigurationInstance method. In the Activity class, he essentially did nothing. At all:

  public Object onRetainNonConfigurationInstance() { return null; } 

Description in the documentation while promising:
If you’re a little bit different, you’ll have to create a new one. You can retrieve your event by yourself.

image

That is, that there is no sun - it will come out in getLastNonConfigurationInstance () after recreating the Activity. The developers of architectural components took advantage of this. Of the minuses - up to 4 android does not work, there will have the old fashioned way through the retain fragment.

The ViewModel's clear () method was called extremely simply - in the onDestroy FragmentActivity method.

  protected void onDestroy() { super.onDestroy(); if (this.mViewModelStore != null && !this.isChangingConfigurations()) { this.mViewModelStore.clear(); } this.mFragments.dispatchDestroy(); } 

In fact, with Androidx, almost everything is the same, the only difference is that the getViewModelStore () method is no longer in FragmentActivity, but in ComponentActivity , from which FragmentActivity is inherited in AndroidX. Only the call to the clear () method has changed, it was taken from onDestroy to an independent callback that is created in the ComponentActivity constructor:

  getLifecycle().addObserver(new GenericLifecycleObserver() { @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } }); 

For the record - during the creation of the article were used:

Support library 27.0.0, 28.0.0
androidx.lifecycle: lifecycle-viewmodel: 2.0.0
androidx.lifecycle: lifecycle-extensions: 2.0.0
android.arch.lifecycle: extensions: 1.1.1
android.arch.lifecycle: viewmodel: 1.1.1

Findings:


- ViewModel did survive the re-creation of activity in the retain fragment until the Support library 27.1.0 appeared in February 2018
- C version Support library 27.1.0 and further, as well as in AndroidX ViewModel went to wait for the re-creation of the Activity in FragmentActivity.NonConfigurationInstances ( ComponentActivity.NonConfigurationInstances for AndroidX), in fact by the same mechanism through which retain fragments work, but creating an extra fragment is not required , all ViewModel are sent “near” with retain fragments.
- The mechanism of the ViewModel is almost the same in AndroidX and the Support library
- If you suddenly need (yes, I can’t even imagine why) you can drag through the data that the Activity should live while the re-creation is alive - you can use the onRetainNonConfigurationInstance () / getLastNonConfigurationInstance () binding
- That the old decision, that the new look is something between a documented hack and crutches

Source: https://habr.com/ru/post/439926/