Android ViewModel and LiveData

  • 2021-11-29 08:39:53
  • OfStack

The ViewModel class is designed to store and manage UI-related data in a lifecycle-focused manner.

For example, when the configuration of Activity changes (screen rotation), Activity is recreated and the onCreate () method is called again. We can save the data in the onSaveInstanceState () method and recover the data from the onCreate () method through Bundle, but this approach is only suitable for small amounts of data that can be serialized, not potentially large amounts of data. If you use ViewModel, ViewModel will automatically retain the previous data and give it to the new Activity or Fragment. Until the current Activity is destroyed by the system, Framework will call the onCleared () method of ViewModel, and we can do some resource cleaning operations in the onCleared () method.

LiveData is an observable data holder class. Unlike common observers, LiveData is lifecycle aware. This means that it respects the lifecycle of other application components, such as Activity, Fragment, or Service. This awareness ensures that LiveData updates only application components that are in the lifecycle state.

LiveData is considered active by the observer represented by the observer class if its lifecycle is either STARTED or RESUMED. LiveData treats observers as active and notifies them of changes in data. LiveData unregistered observers and inactive observers will not be notified of updates.

Advantages of LiveData:

Ensure the data status of UI interface

LiveData follows the observer pattern. LiveData notifies Observer objects when the lifecycle state changes and updates UI in these Observer objects. The observer can update the UI every time the application data changes, instead of updating the UI every time a change occurs.

No memory leaks

When observers are bound to their corresponding LifeCycle, they are automatically removed when the page is destroyed without causing a memory overflow.

Crash will not be caused by the invisibility of Activity

When Activity is not visible, LiveData does not notify the observer even if there are data changes. Because the observer's LifeCyele is not in the Started or RESUMED state at this time.

Configuration changes

When the current Activity configuration changes (e.g. screen orientation), resulting in a re-walk through the onCreate, the observer immediately receives the latest data before the configuration change.

There is no need to deal with the life cycle artificially

Activity or Fragment only need to observe the data when it is necessary to observe the data, and do not need to pay attention to the life cycle changes. All these cuts are handed over to LiveData for automatic management.

Adding dependencies for the ViewModel and LiveData libraries


//build.gradle In a file 
allprojects {
 repositories {
  google()
  jcenter()
 }
}
//app/build.gradle In a file 
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-alpha1'

Define ViewModel and create LiveData


package io.dcloud.H56580E2E.viewModelLiveData;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
/**
 * ViewModel  The main solution is Fragment Parameter transfer problem based on 
 * ViewMode And object instances, that is ViewModel Won't be due to ConfigurationChange And change. For example, when your mobile phone is flipped, activity Will go back to the life cycle. 
 *  So if you are in Activity It is very likely that the data saved in is overwritten, reset or lost. But if we will activity As View Component, then our key attributes will naturally be stored in the ViewModel Medium. 
 *  At this time if acitivty Adj. ConfigrationChange Called because the activity Object has not been reconstructed, and it is still the previous object, so what we get ViewModel It won't change 
 */

/**
 *  Definition ViewModel And create LiveData
 * ViewModel Is stored and managed in the way of associated life cycle UI The class of related data, even if configuration When changes occur (such as rotating the screen), the data can still exist without being destroyed ( Use ViewModel Just inherit  AndroidViewModel/ViewModel )
 * AndroidViewModel(ViewModel Subclass of ) : Need to use Context Object when it is inherited   Otherwise inherit  ViewModel
 *
 * LiveData Yes 1 Observable data holder classes 
 * ViewModel  And  LiveData Cooperate with each other 
 */
public class DomeModel extends ViewModel {
 // Create LiveData( You can create multiple different types of  LiveData..)
 private MutableLiveData<DomeInfo> mDomeLiveData;

 /**
  *  Change  LiveData  Data in 
  *  Use  setValue()
  * @param phone_str
  * @param pwd_str
  * setValue() To call in the main thread 
  */
 public void setDomeInfo(String phone_str,String pwd_str){
  mDomeLiveData.setValue(new DomeInfo(phone_str,pwd_str));
 }

 /**
  *  Change  LiveData  Data in 
  *  Use  postValue()
  * postValue() Can be called in both the main thread and child threads 
  */
 public void postDomeInfo(String phone_str,String pwd_str){
  mDomeLiveData.postValue(new DomeInfo(phone_str,pwd_str));
 }

 /**
  *
  * @return
  */
 public MutableLiveData<DomeInfo> getmDomeLiveData(){
  if(mDomeLiveData == null){
   mDomeLiveData = new MutableLiveData<>();
  }
  return mDomeLiveData;
 }
 //  When MyActivity When destroyed, Framework Will call ViewModel Adj. onCleared()
 @Override
 protected void onCleared() {
  super.onCleared();
 }
}

Use (demonstrating communication between Activity and Fragment and between Fragment and Fragment)

In Activity:


package io.dcloud.H56580E2E.viewModelLiveData;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.dcloud.H56580E2E.R;
import io.reactivex.functions.Consumer;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import com.jakewharton.rxbinding3.view.RxView;

import java.util.concurrent.TimeUnit;

public class DomeActivity extends AppCompatActivity {
 @BindView(R.id.button)
 Button addBut;
 @BindView(R.id.textView3)
 TextView showData_text;
 //ViewModel  Object 
 private DomeModel domeModel;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_dome);
  ButterKnife.bind(this);

  // Add  Fragment View 
  getSupportFragmentManager().beginTransaction().replace(R.id.frameLayout1,new OneFragment()).commit();
  getSupportFragmentManager().beginTransaction().replace(R.id.frameLayout2,new TwoFragment()).commit();
  // Initialization  ViewModel
  domeModel = ViewModelProviders.of(this).get(DomeModel.class);
  //Button Button click event 
  RxView.clicks(addBut).throttleFirst(2, TimeUnit.SECONDS)
    .subscribe(new Consumer<Object>() {
     @Override
     public void accept(Object unit) throws Exception {
      // Use  postValue() Method update 1 Bar data 
      domeModel.postDomeInfo("13233253173","11111111");
     }
    });
  // Get   The data updated above 
  domeModel.getmDomeLiveData().observe(this, new Observer<DomeInfo>() {
   @Override
   public void onChanged(DomeInfo domeInfo) {
    showData_text.setText(" Account number :"+domeInfo.getPhone_str()+" Password :"+domeInfo.getPwd_str());
   }
  });
 }
}

Fragment 1:


package io.dcloud.H56580E2E.viewModelLiveData;

import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.jakewharton.rxbinding3.view.RxView;

import java.util.concurrent.TimeUnit;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.dcloud.H56580E2E.R;
import io.reactivex.functions.Consumer;


public class OneFragment extends Fragment {
 //ButterKnife Object 
 private Unbinder unbinder;
 // Create  ViewModel  Object 
 private DomeModel domeModel;
 @BindView(R.id.textView)
 TextView show_textview;
 @BindView(R.id.button2)
 Button update_but;
 public OneFragment() {
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
 }

 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
  View view= inflater.inflate(R.layout.one_fragment, container, false);
  unbinder=ButterKnife.bind(this,view);
  return view;
 }

 /*onViewCreated Is in onCreateView Events triggered after */
 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
 }

 @Override
 public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
  domeModel=ViewModelProviders.of(getActivity()).get(DomeModel.class);
  // In  oneFragment Update data in 
  RxView.clicks(update_but).throttleFirst(2,TimeUnit.SECONDS)
    .subscribe(new Consumer<Object>() {
     @Override
     public void accept(Object unit) throws Exception {
      domeModel.postDomeInfo("fragement1323325317","123456");
     }
    });

  //  Get updated data ( Combine  RxJava2  Operator )
  domeModel.getmDomeLiveData().observe(this, new Observer<DomeInfo>() {
   @Override
   public void onChanged(DomeInfo domeInfo) {
    show_textview.setText(" Account number :"+domeInfo.getPhone_str()+" Password :"+domeInfo.getPwd_str());
   }
  });
 }

 @Override
 public void onDestroyView() {
  super.onDestroyView();
  //Fragment Untie when destroyed  ButterKnife
  unbinder.unbind();
 }
}

Fragment 2:


package io.dcloud.H56580E2E.viewModelLiveData;

import android.content.Context;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.jakewharton.rxbinding3.view.RxView;

import java.util.concurrent.TimeUnit;

import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.dcloud.H56580E2E.R;
import io.reactivex.functions.Consumer;

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * create an instance of this fragment.
 */
public class TwoFragment extends Fragment {
 @BindView(R.id.textView5)
 TextView show_textview;
 @BindView(R.id.button3)
 Button button;
 private DomeModel domeModel;
 private Unbinder unbinder;

 public TwoFragment() {
  // Required empty public constructor
 }



 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
 }

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
  // Inflate the layout for this fragment
  View view= inflater.inflate(R.layout.fragment_two, container, false);
  unbinder=ButterKnife.bind(this,view);
  return view;
 }

 @Override
 public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
  domeModel = ViewModelProviders.of(getActivity()).get(DomeModel.class);
  RxView.clicks(button).throttleFirst(2,TimeUnit.SECONDS)
    .subscribe(new Consumer<Object>() {
     @Override
     public void accept(Object unit) throws Exception {
      domeModel.postDomeInfo("fragement2-1323325317","123456");
     }
    });
  //  Get updated data ( Combine  RxJava2  Operator )
  domeModel.getmDomeLiveData().observe(this, new Observer<DomeInfo>() {
   @Override
   public void onChanged(DomeInfo domeInfo) {
    show_textview.setText(" Account number :"+domeInfo.getPhone_str()+" Password :"+domeInfo.getPwd_str());
   }
  });
 }

 @Override
 public void onAttach(Context context) {
  super.onAttach(context);
 }

 @Override
 public void onDetach() {
  super.onDetach();
 }

 @Override
 public void onDestroyView() {
  super.onDestroyView();
  //Fragment Untie when destroyed  ButterKnife
  unbinder.unbind();
 }
}

Dome Entity Class:


package io.dcloud.H56580E2E.viewModelLiveData;

/**
 * Dome Entity class 
 */
public class DomeInfo {
 private String phone_str;
 private String pwd_str;

 public DomeInfo(String phone_str, String pwd_str) {
  this.phone_str = phone_str;
  this.pwd_str = pwd_str;
 }

 public DomeInfo() {
 }

 public String getPhone_str() {
  return phone_str;
 }

 public void setPhone_str(String phone_str) {
  this.phone_str = phone_str;
 }

 public String getPwd_str() {
  return pwd_str;
 }

 public void setPwd_str(String pwd_str) {
  this.pwd_str = pwd_str;
 }
}

Related articles: