LiveData and ViewModel are both official Android Architecture Components.
1. Introduction
Although this article is all about LiveData and ViewModels, it more or less touches on another component: Lifecycles. Both of them, along with Room, were launched at Google IO in ’17 as previews, and were released in full form around the end of’ 17. After this year’s IO conference, many new members were added.
You can see that the 27.0.0 V7 library has dependencies on Lifecycles.
Lifecycles was integrated into SupportActivity at the time.
I didn’t take it too seriously at first… Until after 27.1.0:
Well, here comes today’s hero, LiveData and ViewModel. I think it’s time to learn something.
By the way, take a look at the latest V7 to date:
I found that many common components, such as ViewPager and SwipeRefreshLayout, have separate V4 packages, but I won’t go into that here.
Advantage of 2.
LiveData is a data container that can sense the Activity and Fragment life cycle. When the data held by LiveData changes, it notifies the corresponding interface code to update it. At the same time, LiveData holds a reference to the interface code Lifecycle, which means it will make updates when the Lifecycle of the interface code is started or resumed, And stop updating when LifecycleOwner is destroyed.
The above description describes the benefits of LiveData: no manual control of the life cycle, no worries about memory leaks, and notification of data changes.
The ViewModel separates the view’s data and logic from entities with lifecycle characteristics, such as activities and fragments. The ViewModel does not disappear until the associated Activity or Fragment is completely destroyed, that is, the view data is retained even in the event that a rotation of the screen causes the Fragment to be recreated. ViewModels not only eliminate common lifecycle issues, but also help build a more modular and testable user interface.
The advantages of viewModels are also obvious, storing data for activities and fragments until they are completely destroyed. Especially for screen rotation scenarios, the common method is to save data through onSaveInstanceState() and then restore it in onCreate(), which is really troublesome.
Second, because the ViewModel stores data, the ViewModel can share data within the Fragment of the current Activity.
So the combination of LiveData and ViewModel can be said to be a combination of two swords, while Lifecycles run through it.
3. Basic use
Here we use the official Demo to illustrate briefly,
1. Data storage
/**
* A ViewModel used for the {@link ChronoActivity3}.
*/
public class LiveDataTimerViewModel extends ViewModel {
private static final int ONE_SECOND = 1000;
private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();
private long mInitialTime;
public LiveDataTimerViewModel() {
mInitialTime = SystemClock.elapsedRealtime();
Timer timer = new Timer();
// Update the elapsed time every second.
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
// setValue() cannot be called from a background thread so post to main thread.
mElapsedTime.postValue(newValue);
}
}, ONE_SECOND, ONE_SECOND);
}
public LiveData<Long> getElapsedTime() {
return mElapsedTime;
}
}Copy the code
The LiveDataTimerViewModel is simple, starting a scheduled task at initialization and refreshing the data every second through the postValue method.
public class ChronoActivity3 extends AppCompatActivity { private LiveDataTimerViewModel mLiveDataTimerViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.chrono_activity_3); mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class); subscribe(); } private void subscribe() { final Observer<Long> elapsedTimeObserver = new Observer<Long>() { @Override public void onChanged(@Nullable final Long aLong) { String newText = ChronoActivity3.this.getResources().getString( R.string.seconds, aLong); ((TextView) findViewById(R.id.timer_textview)).setText(newText); Log.d("ChronoActivity3", "Updating timer"); }}; // mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver); }}Copy the code
The Activity is also simple: create an observer, elapsedTimeObserver. When data changes in the LiveDataTimerViewModel, it receives the latest data. Of course your page should be STARTED or RESUMED. Unless you use observeForever to look at the data, you can look at the source code to see how it works.
mLiveDataTimerViewModel.getElapsedTime().observeForever(elapsedTimeObserver);Copy the code
2. Share data between Fragmnet
public class SeekBarViewModel extends ViewModel {
public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
}Copy the code
The SeekBarViewModel stores data of type Integer.
public class Fragment_step5 extends Fragment { private SeekBar mSeekBar; private SeekBarViewModel mSeekBarViewModel; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_step5, container, false); mSeekBar = root.findViewById(R.id.seekBar); GetActivity () mSeekBarViewModel = ViewModelproviders.of (getActivity()).get(seekBarViewModel.class); subscribeSeekBar(); return root; } private void subscribeSeekBar() { Update the data in a ViewModel. MSeekBar. SetOnSeekBarChangeListener (new SeekBar. OnSeekBarChangeListener () {@ Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { Log.d("Step5", "Progress changed!" ); mSeekBarViewModel.seekbarValue.setValue(progress); }}... }); // Update SeekBar as ViewModel data changes. mSeekBarViewModel.seekbarValue.observe(this, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer value) { if (value ! = null) { mSeekBar.setProgress(value); }}}); }}Copy the code
Effect:
This page is a Fragment with Fragment_step5 on top and Fragment_step5 on top and SeekBar on top. The effect is to drag one SeekBar, and the SeekBar on the other side will change as well.
4. Simplify use
Here I’ve written a little tool library called Saber to handle (ok, caught off guard ads…) , using the Annotation Processor to automatically generate cumbersome code.
Start by creating a class that uses the @LiveData annotation to mark the data you want to save. Notice the parameter names here, which will be used next.
public class SeekBar {
@LiveData
Integer value;
}Copy the code
Build — > Make Project generates the following code:
public class SeekBarViewModel extends ViewModel { private MutableLiveData<Integer> mValue; public MutableLiveData<Integer> getValue() { if (mValue == null) { mValue = new MutableLiveData<>(); } return mValue; } public Integer getValueValue() { return getValue().getValue(); } public void setValue(Integer mValue) { if (this.mValue == null) { return; } this.mValue.setValue(mValue); } public void postValue(Integer mValue) { if (this.mValue == null) { return; } this.mValue.postValue(mValue); }}Copy the code
Provides common operations for the ViewModel. SetXXX () is called on the main thread, while postXXX() can be called on either the main thread or child threads. In general, it can be used directly. Take the Fragment example above. Simplified as:
public class TestFragment extends Fragment { private SeekBar mSeekBar; @bindViewModel (isShare = true) //<-- mark the ViewModel to bind to; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_test, container, false); mSeekBar = root.findViewById(R.id.seekBar); Saber.bind(this); // <-- bind ViewModel subscribeSeekBar(); return root; } private void subscribeSeekBar() { mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { mSeekBarViewModel.setValue(progress); }}... }); } @onchange (model = "mSeekBarViewModel") //<-- receive changes void setData(Integer value){ = null) { mSeekBar.setProgress(value); }}}Copy the code
By default, @bindViewModel is used to store data. If you want to share data between fragments, @bindViewModel (isShare = true) is required. Ensure that the same key value is passed in. The default key value is the canonical name of the class, which is the package name plus the class name.
Therefore, data cannot be shared if the Fragment class name or package name is different. @bindViewModel (key = “value”)
For the first example we can use:
Public class LiveDataTimerViewModel extends TimerViewModel {// <-- private static final int ONE_SECOND = 1000; private long mInitialTime; public LiveDataTimerViewModel() { mInitialTime = SystemClock.elapsedRealtime(); Timer timer = new Timer(); // Update the elapsed time every second. timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000; // setValue() cannot be called from a background thread so post to main thread. postTime(newValue); //<-- use post directly. } }, ONE_SECOND, ONE_SECOND); }}Copy the code
The Activity is as follows:
public class ChronoActivity3 extends AppCompatActivity {
private TextView textView;
@BindViewModel
LiveDataTimerViewModel mTimerViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = this.findViewById(R.id.tv);
Saber.bind(this); // <-- 绑定
}
@OnChange(model = "mTimerViewModel")
void setData(Long time){
String newText = MainActivity.this.getResources().getString(R.string.seconds, time);
textView.setText(newText);
Log.d("ChronoActivity3 ", "Updating timer");
}
}
Copy the code
Isn’t it more concise to use? If a page has multiple viewModels, the effect may be more obvious.
Principle 5.
Because the implementation borrows heavily from ButterKnife, the method of use is almost identical to butterKnife. Think of butterknife’s @bindView and @onclick.
In fact, the principle is not complicated, is to generate a class to help us to fetch the ViewModel and implement changes in the data monitoring. As follows:
public class MainActivity_Providers implements UnBinder { private MainActivity target; @UiThread public MainActivity_Providers(MainActivity target) { this.target = target; init(); } private void init() { target.mTimerViewModel = ViewModelProviders.of(target).get(LiveDataTimerViewModel.class); target.mTimerViewModel.getTime().observe(target, new Observer<Long>() { @Override public void onChanged(Long value) { target.setData(value); }}); } @CallSuper @UiThread public void unbind() { MainActivity target = this.target; if (target == null) { throw new IllegalStateException("Bindings already cleared."); } this.target = null; }}Copy the code
All codes have been uploaded to Github. We hope you can give us a lot of support! What problems and suggestions can also be raised Issues, let us slowly improve him.
6. Reference
-
1. Release stable Version 1.0 of Android architecture components
-
2.【 Reveal 】Android architecture component ViewModel context