Android Jetpack Components of ViewModel Learning Notes

In the last article: Android Jetpack Components of LiveData Learning Notes

Demo address: https://github.com/mengzhinan/Lifecycle_LiveData_ViewModel_demo

ViewModel Google Document: https://developer.android.google.cn/topic/libraries/architecture/viewmodel

 

Environmental configuration:

Unlike Lifecycle and LiveData, dependencies need to be imported:

// Configuration to be added to viewmodel
implementation 'android.arch.lifecycle:extensions:1.1.1'

What is ViewModel?

The ViewModel class is designed to store and manage UI-related data in a lifecycle-aware manner. The ViewModel class allows data to survive configuration changes, such as screen rotation.

 

In my understanding, it can be summarized as follows:

1. ViewModel can share data among multiple Fragment s under the same Activity object.

2. ViewModel can realize data sharing before and after the rotation of mobile screen to avoid unnecessary repeated data requests.

 

Let's start with Demo. LiveData Demo in the previous article:

public class DataUtil {

    private MutableLiveData<String> name = new MutableLiveData<>();

    public LiveData<String> getNameLiveData(){
        return name;
    }

    public void requestHead() {
        // Simulate the request network or DB, and then update the data
        name.setValue("requestHead success");
    }

    public void requestList() {
        // Simulate the request network or DB, and then update the data
        name.setValue("requestList success");
    }

}
public class LiveDataActivity extends AppCompatActivity {

    private DataUtil dataUtil;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        dataUtil = new DataUtil();
        dataUtil.getNameLiveData().observe(this, data -> {
            // Get data updates



        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        dataUtil.requestHead();
        dataUtil.requestList();
    }
}

There is a problem with the above code. If DataUtil is created repeatedly, the MutableLiveData object will be recreated accordingly, so data sharing cannot be achieved. Don't drill a bull's horn. Look at the use of ViewModel.

public class ListViewModel extends ViewModel {
    private MutableLiveData<String> headLiveData;
    private MutableLiveData<String> listLiveData;

    public LiveData<String> getHeadLiveData() {
        if (headLiveData == null) {
            headLiveData = new MutableLiveData<>();
        }
        return headLiveData;
    }

    public LiveData<String> getListLiveData() {
        if (listLiveData == null) {
            listLiveData = new MutableLiveData<>();
        }
        return listLiveData;
    }

    public void requestHead() {
        // TODO: 2019-07-29
        headLiveData.setValue("head request success");
    }

    public void requestList() {
        // TODO: 2019-07-29
        listLiveData.setValue("list request success");
    }
}
public class ViewModelActivity extends AppCompatActivity {

    private ListViewModel listViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
        listViewModel.getHeadLiveData().observe(this, data->{
            // Header Data Update

        });
        listViewModel.getListLiveData().observe(this, data->{
            // list data update

        });

    }

    @Override
    protected void onResume() {
        super.onResume();
        listViewModel.requestHead();
        listViewModel.requestList();
    }
}

Code description:

The DataUtil logic was changed to ListViewModel, and then the ViewModel class was inherited. Others have no essential change.

In ViewModel Activity, the only change is that the way ListViewModel is created becomes:

listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);

 

 

Let's first analyze the ViewModel class:

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

This is an abstract class, in which there is only one method, onCleared(), which is called back to clean up resources when Activeness where ViewModel resides is destroyed.

ViewModel has a more commonly used subclass, AndroidViewModel, which provides application objects:

/**
 * Application context aware {@link ViewModel}.
 * <p>
 * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
 * <p>
 */
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

 

 

Let's go back to the method of ViewModel acquisition:

ViewModelProviders.of(this).get(ListViewModel.class);

The get() method is traced first:

@NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

Continue to track get() overloading methods:

@NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

If the passed ViewModel is cached, the cached ViewModel object is returned directly; otherwise, a new object is created.

So how does mViewModel Store cache?

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

It was found that mViewModel Store was assigned in the constructor of ViewModel Provider. Tracing back to the ViewModel Providers of method:

@NonNull
    @MainThread
    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);
    }

Tracking back to the ViewModel Stores of method:

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

Find the holder FragmentFor () method in HolderFragment:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

As a matter of fact, we can see the basic principles here:

Create a HolderFragment object in Activity and add it to the Fragment Manager. Save the ViewModel collection in HolderFragment.

Therefore, when Activity is destroyed, Holder Fragment is destroyed synchronously.

So why don't the ViewModel be destroyed before and after the screen is rotated?

Tracing code, constructor of HolderFragment class:

public HolderFragment() {
        setRetainInstance(true);
    }
setRetainInstance(true); this line of code should be set to avoid rebuilding Fragment when the screen rotates.

 

So, summarize the features of ViewModel:

ViewModel is stored in Activity's Fragment Manager attached to Holder Fragment. ViewModel is acquired by ViewModel Providers. of (this). get (xxxx) in any location and fragment under the same activity. It will be the same object, thus avoiding repeated requests and realizing data sharing.

Because of Holder Fragment's characteristics, it can avoid data loss and duplicate requests after screen rotation.

 

 

Demo address: https://github.com/mengzhinan/Lifecycle_LiveData_ViewModel_demo

 

 

 

 

 

Tags: Fragment Android github Google

Posted on Wed, 28 Aug 2019 02:29:29 -0700 by The Stewart