Android jetpack ----- ViewModel basic usage

1. Bridge between view and data model

When the function of the page (Activity/Fragment) is relatively simple, we usually write all the business logic related to UI interaction and data acquisition in the page. However, in the case of complex page functions, the amount of code will change very much, which also violates the "single function principle". The page should only be responsible for handling the interaction between users and UI controls and displaying the data on the screen, while the business logic related to data acquisition should be processed and stored separately.
To solve this problem, Android provides us with the ViewModel class, which is specially used to store the data required by the page.
ViewModel can be understood as something between view and model. It acts as a bridge so that views and data can be separated and communication can be maintained.

2. Life cycle of ViewModel

As shown in the figure below:

The life cycle of ViewModel will be longer than that of the Activity and Fragment that created it. That is, the data in the ViewModel will always survive in the Activity/Fragment.

As we all know, due to the particularity of the Android platform, if the application sends the screen rotation, it will experience the destruction and reconstruction of the Activity. Here is the problem of data saving. Although the Activity can save and recover data through the onSaveInstanceState() mechanism, the onSaveInstanceState() method can only store a small amount of data for recovery, but what should I do when I encounter a large amount of data?

Fortunately, ViewModel can solve this problem perfectly. ViewModel has its own independent life cycle. The reconstruction of Activity caused by screen rotation will not affect the life cycle of ViewModel

3. Use of ViewModel


Add dependency
Add dependency in build.gradle of app

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

Write a class that inherits ViewModel and name it TimerViewModel

public class TimerViewModel extends ViewModel {
    @Override
    protected void onCleared() {
        super.onCleared();
    }
} 

ViewModel is an abstract class with only one onCleared() method. When the ViewModel is no longer needed, that is, the related activities are destroyed, this method will be called by the system. We can perform some operations related to resource release in this method. Note: this method will not be called when the Activity is rebuilt due to screen rotation.

As mentioned earlier, the most important function of ViewModel is to separate the view from the data and independent of the reconstruction of Activity. In order to verify this. We create a Timer timer in the ViewModel. Every 1s, the notification interface notifies its caller. The code is as follows
 

public class TimerViewModel extends ViewModel {
    private Timer timer;
    private  int currentSecond;
    @Override
    protected void onCleared() {
        super.onCleared();
        //Clean up resources
        timer.cancel();
    }

    //Start timing
    public  void startTiming(){
        if(timer == null){
            currentSecond = 0;
            timer = new Timer();
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    currentSecond++;
                    if(onTimeChangeListener != null){
                        onTimeChangeListener.onTimeChanged(currentSecond);
                    }
                }
            };
            timer.schedule(timerTask,1000,1000);
        }
    }
    private  OnTimeChangeListener onTimeChangeListener;

    public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
        this.onTimeChangeListener = onTimeChangeListener;
    }

    public  interface  OnTimeChangeListener{
        void onTimeChanged(int second);
    }

}

We can release timer resources in onCleared() to prevent memory leakage. In fact, this method is not very good. A better way is to implement it through the LiveData component, which I will talk about later.


Write Activity

The instantiation process of ViewModel is completed through ViewModelProvider. The ViewModelProvider will judge whether the ViewModel exists. If it exists, it will return directly. Otherwise, it will create a ViewModel. The code is as follows:
 

public class ViewModelActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.viewmodel);

        initComp();
    }

    private void initComp(){
        final TextView textView = findViewById(R.id.text);
        TimerViewModel timerViewModel = new ViewModelProvider(this).get(TimerViewModel.class);

        timerViewModel.setOnTimeChangeListener(new TimerViewModel.OnTimeChangeListener() {
            @Override
            public void onTimeChanged(final int secound) {
                //Update UI
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("Update time:"+secound);
                    }
                });
            }
        });

        timerViewModel.startTiming();
    }
}

When we rotate the screen and cause the Activity to rebuild, the timer does not stop. This means that the ViewModel corresponding to the Activity in the horizontal / vertical screen state is the same, has not been destroyed, and the data held always exists.

Principle of ViewModel

We instantiate the ViewModel through the ViewModelProvider class in the page

 TimerViewModel timerViewModel =  new ViewModelProvider(this).get(TimerViewModel.class);

Check the source code of ViewModelProvider and know that the constructor of ViewModelProvider receives a ViewModelStoreOwner object as a parameter

 public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

Seeing this, do you all have a question that the ViewModelProvider needs to pass in the ViewModelStoreOwner object? Why can we instantiate it by passing in this (current Activity)?
Because TimerActivity inherits from AppCompatActivity, AppCompatActivity inherits from FragmentActivity, FragmentActivity inherits from ComponentActivity, and ComponentActivity implements the ViewModelStoreOwner interface, one of which is
The getViewModelStore() method returns ViewModelStore
 

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner 
.....

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

ViewModelStore source code

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);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

From the source code of ViewModelStore, it can be seen that ViewModel is actually cached in the form of HashMap < string, ViewModel >. There is no direct association between ViewModel and page. They are associated through ViewModelProvider. When the page needs a ViewModel, it will ask the ViewModelProvider, and the ViewModelProvider will check whether the ViewModel already exists in the cache in the HashMap. If so, it will return directly. Otherwise, it will instantiate one. Therefore, the destruction and reconstruction of Activity due to screen rotation will not affect ViewModel

However, when using the ViewModel, we should pay attention not to pass any type of Context or objects with Context references into the ViewModel, which may cause the page to be unable to be destroyed and cause memory leakage.
It should be noted that in addition to activity, the Fragment also implements the ViewModelStoreOwner interface by default. Therefore, we can also use ViewModel normally in the Fragment;

ViewModel and Android ViewModel

If you want to use the Context object in VIewModel (for example, to find system services), you can use the AndroidViewModel class, which inherits from ViewMoel and receives Application as Context. Because the Application will extend the Context, the code is as follows
 

public class TimerViewModel extends AndroidViewModel {
	 public TimerViewModel(@NonNull Application application) {
        super(application);
    }
}

summary

1.ViewModel can help us better separate the page and data from the code level. Rotating the screen will not affect

The ViewModel's life cycle and data will not be affected. There is no association between the ViewModel and the interface. It is through

The ViewModelProvider is associated. When the ViewModel is needed, ask the ViewModelProvider,

The ViewModelProvider checks whether it is cached. If it exists, it returns directly. If it does not exist, it instantiates one. Therefore, the Activity

The destruction and reconstruction caused by the change does not affect the ViewModel.

2. When we use ViewModel, we cannot pass any type of Context or object with Context reference to ViewModel

Otherwise, the page cannot be destroyed, resulting in memory leakage

3. If you want to use Context, you can use the AndroidViewModel class, which inherits the ViewModel. And receive

Application is used as Context because its life cycle is the same as that of application

 

Reference documents

ViewModel overview  |  Android Developer  |  Android Developers

ViewModel component of Android architecture_ White gourd into the World blog - CSDN blog

Keywords: Android jetpack

Added by rodrigocaldeira on Tue, 21 Sep 2021 21:59:03 +0300