ViewModel
Introduction
When the page (Activity/Fragment) is very simple, usually we will write UI interaction, data acquisition and processing and other related business logic in the page, but in the case of complex pages, it is not appropriate to do so. It doesn't conform to the " Single Responsibility " principle. The page should only be responsible for receiving user interaction and displaying data on the screen, and relevant data should be stored and processed separately.
To this end, Android provides us with the ViewModel class, which is specially used to store the data required by the application page. It strips the data required by the page from the page, and the page only needs to handle user interaction and display data.
ViewModel is only used to manage UI data, don't let it hold references to View, Activity or Fragment (beware of memory leaks)
effect
- Help the activity to share part of the work, specially used to store data related to the interface. Reduce activity logic (ie code) to a certain extent
- At the same time, it will maintain its own independent life cycle.
For example, when the system configuration changes (such as switching languages, etc.), horizontal and vertical screen switching, etc., it may cause the Activity to be destroyed and rebuilt. Suppose that Activity A needs to be destroyed and Activity B needs to be recreated. Although they both belong to the same type, they are two different instance objects. Therefore, in the process of destroying and rebuilding the Activity, when A is destroyed, the data maintained inside must transition to the reconstructed B, which depends on the ViewModel.The name ViewModel can be understood in this way: it is such a thing between View (view) and Model (model data), it acts as a bridge, so that the view and data can be separated and communicated.
common way to create
Since the life cycle of ViewModel is maintained by the system, it cannot be created directly in the code by means of new .
- Obtain directly based on ViewModelProvider
myViewmodel = new ViewModelProvider(this).get(MyViewmodel.class);
- Created by implementing the ViewModelFactory interface
- First, you need to reconstruct a class so that this class implements the ViewModelProvider.Factory interface
public class VmFactory implements ViewModelProvider.Factory {
private int a;
public VmFactory(int a) {
this.a = a;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T)new MyViewmodel(a);
}
}
- Secondly, get the instance in MainActivity
myViewmodel = new ViewModelProvider(this, new VmFactory(0)).get(MyViewmodel.class);
In fact, the above two methods are ultimately based on ViewModelProvider.Factory to generate ViewModel instances, but if the first method does not pass Factory , the default Factory will be used internally .
life cycle
ViewModel currently has only one life cycle method onCleared() , which is called back when the ViewModel instance object is cleared, that is, when the related Activities are destroyed, this method will be called by the system, and we can execute some resources in this method Release operation to avoid memory leaks.
Note: Since the destruction of ViewModel is judged and executed by the system, how does the system judge it? It is referenced according to Context. Therefore, when we use ViewModel, we must not pass in Activity, Fragment or View from the outside that contains Context references, otherwise the system will think that the ViewModel is still in use, so it cannot be destroyed and recycled by the system, resulting in memory loss. The occurrence of a leak.
use
add dependencies
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
Basic usage - keep data in horizontal and vertical screens
- activity_main layout file
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32dp" />
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="plus one" />
</LinearLayout>
- Create a class MyViewmodel to inherit ViewModel, and all data related to the interface should be placed in ViewModel.
public class MyViewmodel extends ViewModel {
//a用来计数
protected int a=0;
public MyViewmodel(int a) {
this.a = a;
}
}
- MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
MyViewmodel myViewmodel;
TextView textView;
Button plusonebtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//不能直接创建ViewModel的实例,不然每次活动oncreate()都会创建一个实例,无法保留其中数据
//不需传参,使用无参构造函数
myViewmodel = new ViewModelProvider(this).get(MyViewmodel.class);
plusonebtn = findViewById(R.id.plusOneBtn);
textView = findViewById(R.id.infoText);
plusonebtn.setOnClickListener(this);
textView.setText(String.valueOf(myViewmodel.a));
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.plusOneBtn:
myViewmodel.a++;
textView.setText(myViewmodel.a + "");//+空字符串是为了转字符串
break;
default:
break;
}
}
}
pass parameters
Although the above demo will not lose data when the screen is rotated, if it is opened after exiting the program, it will be cleared. Next, we will upgrade this function to ensure that the data will not be lost after the program exits and reopens.
- Modify activity_main.xml
to add a reset button to facilitate users to reset the calculator.
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
......
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/clearbtn"
android:layout_gravity="center_vertical"
android:text="Clear"
/>
</LinearLayout>
- Modify the MyViewmodel class
public class MyViewmodel extends ViewModel {
protected int a;
public MyViewmodel(int countReserved){
a=countReserved;
}
}
- Create a new VmFactory class and let it implement the ViewModelProvider.Factory interface
public class VmFactory implements ViewModelProvider.Factory {
private int a;
public VmFactory(int a) {
this.a = a;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T)new MyViewmodel(a);
}
}
- Modify MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
MyViewmodel myViewmodel;
TextView textView;
Button plusonebtn;
Button clearbtn;
//用来存放数据
SharedPreferences preferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
preferences= PreferenceManager.getDefaultSharedPreferences(this);
int savedCounter=preferences.getInt("count_reserved",0);
//若需要传参数使用这个同时实现ViewModelProvider.Factory
myViewmodel = new ViewModelProvider(this, new VmFactory(savedCounter)).get(MyViewmodel.class);
plusonebtn = findViewById(R.id.plusOneBtn);
clearbtn=findViewById(R.id.clearbtn);
textView = findViewById(R.id.infoText);
plusonebtn.setOnClickListener(this);
clearbtn.setOnClickListener(this);
textView.setText(String.valueOf(myViewmodel.a));
}
//重写onPause():对当前数据进行保存,这样可以保证不管程序退出还是进入后台,计数都不会消失
@Override
protected void onPause() {
super.onPause();
SharedPreferences.Editor editor=preferences.edit();
editor.putInt("count_reserved",myViewmodel.a);
editor.apply();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.plusOneBtn:
myViewmodel.a++;
textView.setText(myViewmodel.a + "");//+空字符串是为了转为字符串
break;
case R.id.clearbtn:
myViewmodel.a=0;
textView.setText(myViewmodel.a+"");
break;
default:
break;
}
}
In the onCreate() method, we first get the instance of SharedPreference, and thenRead the saved data before, if not read, use 0 as the default value. Next, in ViewModelProvider, an additional MainViewModelFactory parameter is passed in, and the read count value is passed to the constructor of MainViewModelFactory.
The next code is that we clear the calculator in the click event of the "Clear" button, and save the current count in the ==onPause() method, so that we can ensure that no matter whether the program exits or enters the background, None of the counts will disappear.