What is the use of ViewModel?
When the application configuration changes, such as screen rotation, language switching, etc., the Activity/Fragment may be destroyed. When it is started again, the data on the Activity/Fragment may be lost, and ViewModel can save these data, and when the application configuration changes, ViewModel objects will be retained, and then the data saved by ViewModel can be used by the next Activity/Fragment, so when processing the data on the page, Make sure to save this data to the ViewModel object.
The following animation shows that after the screen is rotated, the original 1 becomes the original 0, indicating that the data is lost.
How to use ViewModel?
We need to customize a class that inherits from ViewModel, and then declare the values to be saved in it.
public class MyViewModel extends ViewModel { int num = 0; }
Then get an instance of the custom ViewModel in MainActivity.java.
public class MainActivity extends AppCompatActivity { private TextView textView; private Button button; private ActivityMainBinding binding; private MyViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); button = (Button) findViewById(R.id.button); viewModel = new ViewModelProvider(this).get(MyViewModel.class); textView.setText(String.valueOf(viewModel.num)); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewModel.num = viewModel.num + 1; textView.setText(String.valueOf(viewModel.num)); } }); } }
The effect is as follows. You can see that the content in TextView has not changed after the screen is rotated.
LiveData
LiveData can be used to monitor data changes, and then the UI interface can respond. Next, make changes to the custom ViewModel.
Here, we define the data to be saved as MutableLiveData type, not LiveData, because LiveData does not disclose data to the public, and MutableLiveData is a subclass of LiveData. You can set and modify data through setValue and getValue.
public class MyViewModel extends ViewModel { public MutableLiveData<Integer> currentNum; public MutableLiveData<Integer> getCurrentNum() { if(currentNum == null) { currentNum = new MutableLiveData<Integer>(); currentNum.setValue(0); // You must create an instance or set an initial value, otherwise a null pointer exception may occur when calling getValue } return currentNum; } public void add(int num) { currentNum.setValue(currentNum.getValue() + num); } }
Get the MutableLiveData instance according to the MyViewModel instance, and then call the observe method. This method is to monitor the data. Once the data changes, the onChanged method will be executed.
viewModel.getCurrentNum().observe(this, new Observer<Integer>() { @Override public void onChanged(Integer integer) { // TODO view response textView.setText(String.valueOf(integer)); } });
MainActivity.java
public class MainActivity extends AppCompatActivity { private TextView textView; private Button button; private ActivityMainBinding binding; private MyViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); button = (Button) findViewById(R.id.button); viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.getCurrentNum().observe(this, new Observer<Integer>() { @Override public void onChanged(Integer integer) { textView.setText(String.valueOf(integer)); } }); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewModel.add(1); } }); } }
DataBinding implements data binding
Using DataBinding, you can directly modify the content of components such as text in XML files. The specific methods of use are as follows:
First, add the following code to the defaultConfig in the app/build.gradle file.
dataBinding { enabled true }
Then write the code in the XML file that needs data binding as follows.
The type in the variable tag is the class name, which must be complete, and the name is the variable name, which points to this class. The specific use method is:
@{}, and then write Java like code within braces, such as:
android:text="@{String.valueOf(data.getCurrentNum)}"
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="data" type="com.example.viewmodeltest.MyViewModel" /> </data> <!-- This part writes the code to realize the layout--> </layout>
Full code:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="data" type="com.example.viewmodeltest.MyViewModel" /> </data> <!-- This part writes the code to realize the layout--> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="increase" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(data.getCurrentNum)}" android:textSize="24sp" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.503" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.819" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
Then get the DataBinding instance in the MainActivity class, where ActivityMainBinding is in the form of xml file name +Binding.
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main); binding.setData(viewModel); binding.setLifecycleOwner(this);
Full code:
public class MainActivity extends AppCompatActivity { private TextView textView; private Button button; private ActivityMainBinding binding; private MyViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this,R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); button = (Button) findViewById(R.id.button); viewModel = new ViewModelProvider(this).get(MyViewModel.class); binding.setData(viewModel); binding.setLifecycleOwner(this); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { viewModel.add(1); } }); } }
Use SavedStateHandle in ViewModel to avoid data loss caused by process killing
The following first demonstrates the changes of data in the page after the process is killed.
In order to kill this process, we need to enter the developer mode, and then turn on the Don't keep Activities option in the settings, so that as long as the app enters the background, it will be killed.
It can be seen that after the application process is deleted, the data does not keep the content before deletion, but becomes the original 0.
Next, continue to modify MyViewModel and store data into the SavedStateHandle instance in the form of key value pairs.
public class MyViewModel extends ViewModel { private SavedStateHandle handle; static final String KEY = "KEY"; public MyViewModel(SavedStateHandle handle) { this.handle = handle; } public MutableLiveData<Integer> getCurrentNum() { // These can prevent the process from being killed or data loss if(!handle.contains(KEY)){ handle.set(KEY,0); } return handle.getLiveData(KEY); } public void add(int num) { getCurrentNum().setValue(getCurrentNum().getValue() + num); } }
The only modification in the MainActivity class is to modify the method to get the MyViewModel instance.
viewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MyViewModel.class);
The final effect is as follows: the data remains unchanged after the process is killed.