preface

In previous articles, we covered the detailed use of the Lifecycle component in Android Architecture Components and source code parsing. This article will look at two other AAC components, LiveData and ViewModel, which are also implemented using Lifecycle.

What is a LiveData

LiveData is an observable data holding class, but different from the usual observed, LiveData has life cycle awareness. In layman’s terms, LiveData is a “Data” holding class with “Live” capability. LiveData will immediately notify the observer when something that Lifecycle holds changes and an object (such as an Activity or Fragment) is STARTED or RESUMED. In other words, more life cycle awareness than the average observer.

The advantage of LiveData

  1. Make sure the UI and data states match. When data changes, the UI is automatically notified to update it.

  2. Avoiding memory leaks Observers are bound to Lifecycle objects, and they are automatically cleaned up when Lifecycle associated with them is destroyed.

  3. When Lifecycle bound to the Observer is inactive, such as an Activity in the return stack, it will not receive any LiveData events.

  4. The UI component no longer needs to handle the life cycle manually and only listens for the relevant data. It does not need to be concerned about whether listening should be suspended or resumed. LiveData has lifecycle awareness, which automatically manages these.

  5. If Lifecycle is inactive, it will receive the latest data as it changes from inactive to active. For example, when an Activity moves from background to foreground, it immediately receives the latest data

  6. When the system configuration is changed, data is saved and restored, and UI is restored. When an Activity or Fragment is recreated due to a configuration change (such as rotating the screen), it receives the latest available data. A quick point here is that this is something that needs to be used with the ViewModel, and strictly speaking, it’s mainly the advantage of the ViewModel

  7. Resource sharing We can use the singleton pattern to extend LiveData so that all observers are notified of data changes.

In order to understand the relationship between LiveData and ViewModel, I will first say the conclusion:

The purpose of LiveData is to make the data lifecycle aware and automatically call back the callback method in the observer when the Activity, for example, becomes active. In other words, real-time monitoring of changes in data. The ViewModel is used to properly save and restore LiveData when the Activity is rebuilt due to system configuration changes (such as rotating the screen). That’s all.

The use of LiveData

Generally speaking, LiveData needs to be used with ViewModel, but do not think that LiveData must be combined with ViewModel. It also says that the two are complementary. In order to make it easier to understand, let’s first study the use of LiveData separately.

The use of LiveData can be divided into three steps:

  1. Create an instance of LiveData that holds a specific data type, such as String or User. LiveData is usually used in the ViewModel (we’ll use it separately here).

  2. Create an Observer object and implement onChanged(…) Method, which defines what to do when the data held by LiveData changes. UI updates can be made here. Typically, an Observer is created in a UI Controller, such as an Activity or Fragment.

  3. Observe (…) from the created LiveData instance. Method to add an Observer object to LiveData. The prototype of the method is Observe (LifecycleOwner owner, Observer Observer). The first parameter is the LifecycleOwner object, which is the source of LiveData’s ability to listen to the lifecycle. The second argument is our listener object Observer.

Add LiveData and ViewModel dependencies:

1    implementation "android.arch.lifecycle:extensions1.1.1"

Copy the code

Of course, you can also integrate LiveData and ViewModel separately:

1implementation "android.arch.lifecycle:livedata1.1.1"

Copy the code
1implementation "android.arch.lifecycle:viewmodel1.1.1"

Copy the code

Next, create the following code according to the three-step strategy described above:

 1public class MainActivity extends AppCompatActivity implements View.OnClickListener {

2

3    private static final String TAG = "MainActivity";

4

5    private MutableLiveData<Integer> mNumberLiveData;

6    private TextView mTvNumber;

7    private Button mBtnStart;

8

9    @Override

10    protected void onCreate(Bundle savedInstanceState) {

11        super.onCreate(savedInstanceState);

12        setContentView(R.layout.activity_main);

13        mTvNumber = findViewById(R.id.tv_number);

14        mBtnStart = findViewById(R.id.btn_start);

15        mBtnStart.setOnClickListener(this);

16

17

18        mNumberLiveData = new MutableLiveData<>();

19

20        mNumberLiveData.observe(this.new Observer<Integer>() {

21            @Override

22            public void onChanged(@Nullable Integer integer) {

23                mTvNumber.setText("" + integer);

24                Log.d(TAG, "onChanged: " + integer);

25            }

26        });

27    }

28

29    @Override

30    public void onClick(View v) {

31        new Thread() {

32            @Override

33            public void run(a) {

34                super.run();

35                int number = 0;

36                while (number < 5) {

37                    try {

38                        Thread.sleep(3000);

39                    } catch (InterruptedException e) {

40                        e.printStackTrace();

41                    }

42                    number++;

43                    mNumberLiveData.postValue(number);

44                }

45            }

46        }.start();

47    }

48}

Copy the code

Here we create a variable of type mNumberLiveData of MutableLiveData in the onCreate method and specify its generic type as Integer by observing (…) The compatactivity method passes this (which is an AppCompatActivity that implements the LifecycleOwner interface and supports a package of 28.0.0) and passes an Observer that onChanged(…) Method, we set the changed data integer to the TextView to display. For ease of observation, we also print the corresponding log line on the console.

Demo interface is very simple, is a button, a TextView, click the button, open a child thread, every 3 seconds through postValue(…) Modify values in LiveData (if in the UI thread, you can directly setValue(…) To modify).

Here, we click Start and press the Home button to enter the background before the number changes to 5. After some time, when entering the page, we will find that the page finally displays the number “5”, but the printed result is not continuous 1~5, but interrupted:

-w785

This also indicates that when the application goes into the background and becomes inactive, it does not receive notification of data updates, but only when it becomes active again and executes onChanged(…). Methods.

As you can see, when we use LiveData, we actually use its subclass MutableLiveData. LiveData is an interface that does not expose methods to modify the data. If we need to modify the data, we need to use its specific implementation class MutableLiveData, in fact, this class is simply the postValue of LiveData (…) And setValue (…). Lay bare:

 1public class MutableLiveData<Textends LiveData<T{

2    @Override

3    public void postValue(T value) {

4        super.postValue(value);

5    }

6

7    @Override

8    public void setValue(T value) {

9        super.setValue(value);

10    }

11}

Copy the code

MutableLiveData

actually wraps the data. In its generics we can specify our data class. You can store any data, including classes that implement the Collections interface, such as List.

Extension LiveData

Sometimes we need to do something when Lifecycle is active in the Observer, we can do this by inheriting LiveData or MutableLiveData and overwriting its onActive() and onInactive() methods. The default implementations of both methods are null. Something like this:

 1public class StockLiveData extends LiveData<BigDecimal{

2    private StockManager stockManager;

3

4    private SimplePriceListener listener = new SimplePriceListener() {

5        @Override

6        public void onPriceChanged(BigDecimal price) {

7            setValue(price);

8        }

9    };

10

11    public StockLiveData(String symbol) {

12        stockManager = new StockManager(symbol);

13    }

14

15    @Override

16    protected void onActive(a) {

17        stockManager.requestPriceUpdates(listener);

18    }

19

20    @Override

21    protected void onInactive(a) {

22        stockManager.removeUpdates(listener);

23    }

24}

Copy the code

LiveData has lifecycle awareness and can automatically unlisten when an Activity is destroyed, which means it can be used to share data between multiple activities. We can do this by using singletons, which we’ll drink directly from the official Demo:

 1public class StockLiveData extends LiveData<BigDecimal{

2    private static StockLiveData sInstance;

3    private StockManager stockManager;

4

5    private SimplePriceListener listener = new SimplePriceListener() {

6        @Override

7        public void onPriceChanged(BigDecimal price) {

8            setValue(price);

9        }

10    };

11

12    @MainThread

13    public static StockLiveData get(String symbol) {

14        if (sInstance == null) {

15            sInstance = new StockLiveData(symbol);

16        }

17        return sInstance;

18    }

19

20    private StockLiveData(String symbol) {

21        stockManager = new StockManager(symbol);

22    }

23

24    @Override

25    protected void onActive(a) {

26        stockManager.requestPriceUpdates(listener);

27    }

28

29    @Override

30    protected void onInactive(a) {

31        stockManager.removeUpdates(listener);

32    }

33}

Copy the code

Convert LiveData

Sometimes we need to make some changes before distributing data stored in LiveData to the Observer. Mtvnumber.settext (“” + Integer) will return an error if we use mtvNumber.settext (“” + Integer). But WHAT I want to do here is get the String data that I’ve already processed, so I can use it, and I don’t have to do this manually. You can do this through a map operator of the doubling class.

The original code was:

1        mNumberLiveData = new MutableLiveData<>();

2

3        mNumberLiveData.observe(this.new Observer<Integer>() {

4            @Override

5            public void onChanged(@Nullable Integer integer) {

6                mTvNumber.setText("" + integer);

7                Log.d(TAG, "onChanged: " + integer);

8            }

9        });

Copy the code

Using the Transformations. The map (…). The modified code:

 1        mNumberLiveData = new MutableLiveData<Integer>();

2

3        Transformations.map(mNumberLiveData, new Function<Integer, String> () {

4            @Override

5            public String apply(Integer integer) {

6                return "" + integer;

7            }

8        }).observe(this.new Observer<String> () {

9            @Override

10            public void onChanged(@Nullable String s) {

11                mTvNumber.setText(s);

12                Log.d(TAG, "onChanged: " + s);

13            }

14        });

Copy the code

This enables the conversion of one type of data into another type of data. The map operator returns a modified LiveData that you listen on directly. The map operator here is similar to RxJava’s Map.

But sometimes we need to do more than simply convert data from one type to another. We might need something a little more advanced.

For example, we need a LiveData that stores the userId on the one hand and a LiveData that stores the User information on the other hand, and the User of the latter is looked up from the database based on the userId. The two need to correspond. You can use switchMap(…) of the doubling class. Operators.

1MutableLiveData<String> userIdLiveData = new MutableLiveData<>();

2

3LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, new Function<String, LiveData<User>>() {

4    @Override

5    public LiveData<User> apply(String userId) {

6         // Return a LiveData based on the userId, which can be retrieved from Room

7        return getUser(userId);

8    }

9});

Copy the code

Here, we are overwriting apply(…) GetUser (userId) gets a LiveData that encapsulates the User object each time the userId changes. If you get it from a database, use Room, Google’s companion database component, which returns a LiveData directly. About Room, I will write an article to explain it later if I have time.

You can see that the Transformations provided in the LiveData package can be very useful to make the whole call chain. He only provides a map or… And switchMap (…). If we have more complex needs, we need to create our own transformations through the MediatorLiveData class. Anyway, in fact, the above two methods are implemented internally through MediatorLiveData, through MediatorLiveData a forward. You can make a quick check here:

 1public class Transformations {

2

3    private Transformations(a) {

4    }

5

6

7    @MainThread

8    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,

9            @NonNull final Function<X, Y> func)
 
{

10        final MediatorLiveData<Y> result = new MediatorLiveData<>();

11        result.addSource(source, new Observer<X>() {

12            @Override

13            public void onChanged(@Nullable X x) {

14                result.setValue(func.apply(x));

15            }

16        });

17        return result;

18    }

19

20

21    @MainThread

22    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,

23            @NonNull final Function<X, LiveData<Y>> func)
 
{

24        final MediatorLiveData<Y> result = new MediatorLiveData<>();

25        result.addSource(trigger, new Observer<X>() {

26            LiveData<Y> mSource;

27

28            @Override

29            public void onChanged(@Nullable X x) {

30                LiveData<Y> newLiveData = func.apply(x);

31                if (mSource == newLiveData) {

32                    return;

33                }

34                if(mSource ! =null) {

35                    result.removeSource(mSource);

36                }

37                mSource = newLiveData;

38                if(mSource ! =null) {

39                    result.addSource(mSource, new Observer<Y>() {

40                        @Override

41                        public void onChanged(@Nullable Y y) {

42                            result.setValue(y);

43                        }

44                    });

45                }

46            }

47        });

48        return result;

49    }

50}

Copy the code

The source code is relatively simple, no more detailed explanation.

In fact, the main use of MediatorLiveData, through this class we can combine multiple LiveData sources. It is useful that MediatorLiveData Observers are triggered when any of their sources change. For example, we have two LiveData, one from the database and one from the network. MediatorLiveData can do this, and when either of them gets the latest data, it triggers our listening.

Also post the source code for MediatorLiveData, which is derived from MutableLiveData:

 1public class MediatorLiveData<Textends MutableLiveData<T{

2    privateSafeIterableMap<LiveData<? >, Source<? >> mSources =new SafeIterableMap<>();

3

4    @MainThread

5    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {

6        Source<S> e = new Source<>(source, onChanged);

7Source<? > existing = mSources.putIfAbsent(source, e);

8        if(existing ! =null&& existing.mObserver ! = onChanged) {

9            throw new IllegalArgumentException(

10                    "This source was already added with the different observer");

11        }

12        if(existing ! =null) {

13            return;

14        }

15        if (hasActiveObservers()) {

16            e.plug();

17        }

18    }

19

20

21    @MainThread

22    public <S> void removeSource(@NonNull LiveData<S> toRemote) {

23Source<? > source = mSources.remove(toRemote);

24        if(source ! =null) {

25            source.unplug();

26        }

27    }

28

29    @CallSuper

30    @Override

31    protected void onActive(a) {

32        for(Map.Entry<LiveData<? >, Source<? >> source : mSources) {

33            source.getValue().plug();

34        }

35    }

36

37    @CallSuper

38    @Override

39    protected void onInactive(a) {

40        for(Map.Entry<LiveData<? >, Source<? >> source : mSources) {

41            source.getValue().unplug();

42        }

43    }

44

45    private static class Source<Vimplements Observer<V{

46        final LiveData<V> mLiveData;

47        final Observer<V> mObserver;

48        int mVersion = START_VERSION;

49

50        Source(LiveData<V> liveData, final Observer<V> observer) {

51            mLiveData = liveData;

52            mObserver = observer;

53        }

54

55        void plug(a) {

56            mLiveData.observeForever(this);

57        }

58

59        void unplug(a) {

60            mLiveData.removeObserver(this);

61        }

62

63        @Override

64        public void onChanged(@Nullable V v) {

65            if(mVersion ! = mLiveData.getVersion()) {

66                mVersion = mLiveData.getVersion();

67                mObserver.onChanged(v);

68            }

69        }

70    }

71}

Copy the code

By the way, if we want the Observer to be notified immediately when the data is updated, that is, to ignore the lifecycle status, we can use LiveData’s observeForever(Observer

Observer) method.

LiveData often needs to be combined with viewModels to exert greater power. Here’s a look at the ViewModel and how to use it together.

What is the ViewModel

Simply put, a ViewModel is a class that stores and manages UI-related data. The difference, however, is that it automatically saves the data when the system configuration changes. Of course, this works with LiveData.

As we know, when the screen rotates, it will cause the Activity/Fragment to redraw, which will cause our previous data to be lost. For example, if we use EditText, we put something in there, but when the screen rotates, we see that the text is empty. If you don’t, you may be using a control under the support package, or your Activity can inherit from AppCompatActivity and add an ID to that control. The system restores some simple data (in fact, it restores TextView, the parent of EditText).

Simple data can be stored in the Activity’s onSaveInstanceState() method and then recovered in onCreate(), but this is only suitable for storing small amounts of data that can be serialized or deserialized. This is not true for large amounts of data, such as a List of users or bitmaps.

In addition, it makes the View’s data holder and UI Controller logic more separate, making it easier to decouple and test.

LiveData is used in conjunction with ViewModel

Before we used LiveData alone, here we use it with ViewModel:

 1public class MyViewModel extends ViewModel {

2    private MutableLiveData<List<User>> users;

3    public LiveData<List<User>> getUsers() {

4        if (users == null) {

5            users = new MutableLiveData<List<User>>();

6            loadUsers();

7        }

8        return users;

9    }

10

11    private void loadUsers() {

12        // Do an asynchronous operation to fetch users.

13    }

14}

Copy the code

As you can see, here we create a class that inherits from the ViewModel and stores the MutableLiveData fields we need in it. Note that the getUsers() method returns LiveData and not MutableLiveData, because we generally don’t want to modify data outside of the ViewModel, so we return an immutable Reference to LiveData. If we want to make changes to the data, we can expose a setter method.

The ViewModel can then be obtained as follows:

 1public class MyActivity extends AppCompatActivity {

2    public void onCreate(Bundle savedInstanceState) {

3        // Create a ViewModel the first time the system calls an activity's onCreate() method.

4        // Re-created activities receive the same MyViewModel instance created by the first activity.

5

6        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);

7        model.getUsers().observe(this, users -> {

8            // update UI

9        });

10    }

11}

Copy the code

In the onCreate() method, we use viewModelproviders.of (this).get(myViewModel.class); This line of code gets an instance of MyViewModel. The LiveData instance is then retrieved through the getter method exposed by the instance. Note that when the Activity is rebuilt, the onCreate() method is revisited, but the MyViewModel instance is still the same one that was created the first time, It is cached in the get method in viewModelproviders.of (this).get(***.class). After the source code analysis will be explained in detail. To get a sense of the ViewModel lifecycle, take a look at the following diagram:

viewmodel-lifecycle

The ViewModel eventually dies when the Activity is destroyed, and its onCleared() is executed to clear the data.

Fragment Data is shared between fragments

It is common for fragments to share data. A typical example is a Fragment on the left side of the screen that stores a list of news headlines. If we click on an item, the Fragment on the right shows the details of that news item. This scenario is also common with meituan and other food-ordering apps.

Sharing of data between fragments is made easier through the ViewModel.

All we need to do is pass each Fragment’s onCreate() method:

1ViewModelProviders.of(getActivity()).get(* * *ViewModel.class);

Copy the code

To get the ViewModel, note that of(…) Method is passed in the activity of both. For details, please refer to the following official code:

 1public class SharedViewModel extends ViewModel {

2    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

3

4    public void select(Item item) {

5        selected.setValue(item);

6    }

7

8    public LiveData<Item> getSelected(a) {

9        return selected;

10    }

11}

12

13

14public class MasterFragment extends Fragment {

15    private SharedViewModel model;

16    public void onCreate(Bundle savedInstanceState) {

17        super.onCreate(savedInstanceState);

18        / / to the activity

19        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

20        itemSelector.setOnClickListener(item -> {

21            model.select(item);

22        });

23    }

24}

25

26public class DetailFragment extends Fragment {

27    public void onCreate(Bundle savedInstanceState) {

28        super.onCreate(savedInstanceState);

29        / / to the activity

30        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

31        model.getSelected().observe(this, { item ->

32           // Update the UI.

33        });

34    }

35}

Copy the code

The Loader mechanism introduced in Android 3.0 makes it easy for developers to load data asynchronously from activities and fragments. But not many people actually use it. Now it can all but disappear. ViewModel, together with Room database and LiveData, can completely replace Loader. In SDK28, more and more Loader is being replaced.

Note that the ViewModel can be used to replace the Loader, but it is not designed to replace onSaveInstanceState(…). . For more on data persistence and restoring UI state, see this article on Medium. It couldn’t be better: Persistence, onSaveInstanceState(), Restoring UI State and Loaders

conclusion

LiveData is usually used in conjunction with viewModels. The ViewModel is responsible for saving and restoring LiveData when the system configuration changes, and LiveData is responsible for listening for changes to the data when the life cycle state changes.

This is the end of using LiveData and ViewModel. Here, I deliberately separate LiveData and ViewModel at the beginning, which is easier to understand than the official website. However, if you want to understand both in detail, it is recommended to read the official documents carefully several times.

Please follow our official account to get the latest news.