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

    publicstatic ViewModelProvider of(@NonNull FragmentActivity activity){
            return of(activity, null);
        }
        publicstatic ViewModelProvider of(@NonNull FragmentActivity activity,
                @Nullable Factory factory){
            Application application = checkApplication(activity);
            if (factory == null) {
                factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
            }
            returnnew 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 :

    publicstatic 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 :



    publicstatic 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

    publicHolderFragment(){
            setRetainInstance(true);
        }

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

    publicclassViewModelStore{
    		privatefinal HashMap<String, ViewModel> mMap = new HashMap<>();
    		finalvoidput(String key, ViewModel viewModel){
    			ViewModel oldViewModel = mMap.put(key, viewModel);
    			if (oldViewModel != null) {
    				oldViewModel.onCleared();
    			}
    		}
    		final ViewModel get(String key){
    			return mMap.get(key);
    		}
    		publicfinalvoidclear(){
    			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) {
                thrownew 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();
                    }
                }
                returnthis.mViewModelStore;
            }
        }

    Here I will simplify a little - NonConfigurationInstances is an object so that it 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 transfer ViewModel inside the retain fragment, the developers made a tricky move - they used exactly the same mechanism, but got rid of the need to create an extra fragment each time.

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

    public Object onRetainNonConfigurationInstance(){
            returnnull;
        }

    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 method of FragmentActivity.

    protectedvoidonDestroy(){
            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() {
                @OverridepublicvoidonStateChanged(LifecycleOwner source, Lifecycle.Event event){
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        if (!isChangingConfigurations()) {
                            getViewModelStore().clear();
                        }
                    }
                }
            });

    For the protocol, during the creation of the article the following was 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 really survived the re-creation of activity in the retain fragment before the Support library 27.1.0 appeared in February 2018
    - C version of Support library 27.1.0 and further, as well as AndroidX ViewModel went to wait for the re-creation of 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 “side by side” with retain fragments.
    - The mechanism of the ViewModel is almost the same in AndroidX and the Support library
    - If you suddenly need (but can not imagine why) to push data to live long lives Activity but at the same time take into account the re-creation - you can use a bunch of onRetainNonConfigurationInstance () / getLastNonConfigurationInstance ()
    - With the old solution, the new look something between documented hack and crutches

    Also popular now: