This is the 21st day of my participation in Gwen Challenge

LiveData overview

Custom LiveData

We introduced the built-in MutableLiveData, but you can also define your own LiveData class.

Create a BatteryLiveData class that inherits from LiveData, broadcasts to receive system battery power, and sets data to LiveData using setValue.

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) { setValue(price); }};public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive(a) {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive(a) { mStockManager.removeUpdates(mListener); }}Copy the code

The implementation of the price listener shown in this example contains the following methods:

  • whenLiveDataWhen there is an active observer, theonActiveMethod is called. This means that you need to start stock price monitoring in this method.
  • whenLiveDataIn the absence of any active observer, theonInactiveMethod is called. Since no observer is listening, you don’t have toStockManagerContinue the connection.
  • setValue(T)Methods the updateLiveDataValue of the example and notifying each active observer of updates.

You can use StockLiveData like this:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState); LiveData<BigDecimal> myPriceListener = ... ; myPriceListener.observe(this, price -> {
            / / update the UI}); }}Copy the code

The observe() method takes the fragment as LifecycleOwner as the first argument. This is done to indicate that the observer is limited to the Lifecycle of the LifecycleOwner. Therefore:

  • ifLifecycleThe object is not active, so the observer will not be notified if the data changes.
  • whenLifecycleAfter the object is destroyed, the observer is automatically removed.

Singleton LiveData

The fact that LiveData objects are lifecycle aware also means that you can share them with multiple activities, fragments, and Services. To keep the sample code simple, you can implement LiveData in singleton mode like this:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) { setValue(price); }};@MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive(a) {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive(a) { mStockManager.removeUpdates(mListener); }}Copy the code

And use it in the fragment like this:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            / / update the UI}); }}Copy the code

The MyPriceListener sample can be observed with multiple fragments and activities, while LiveData will only connect to system services when at least one or more of them are visible and active.

Use LiveData with Room

todo

The Room data persistence library supports observable queries and returns LiveData objects. Observable queries are part of a Database Access Object (DAO).

When the database is updated, Room automatically generates all the necessary code to update the LiveData object. When necessary, the generated code runs asynchronously on a background thread. This pattern helps to keep the data presented by the UI consistent with the database. For more information about Room and daOs, see the Room Persistence library.

Transformations

todo

Sometimes you may want to make changes to the LiveData object before it distributes updates to the observer, or you may want to return a different LiveData instance based on the value of another LiveData. The Transformations class in the LiveData package provides several helper functions to help you handle these situations.

  • [Transformations. The map ()] (developer. The android. Google. Cn/reference/a… , android.arch.core.util.Function))

    Apply a function transformation to the value stored in the LiveData object and pass the result downwards.

    LiveData<User> userLiveData = ... ; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name +"" + user.lastName
     });
    Copy the code
  • [Transformations. SwitchMap ()] (developer. The android. Google. Cn/reference/a… , android.arch.core.util.Function>))

    Similar to map(), a function transformation is applied to the values stored in the LiveData object and the results are unpacked and passed down. The function transformation must return a LiveData object, as shown in the following example:

    private LiveData<User> getUser(String id) {... ; } LiveData<String> userId = ... ; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );Copy the code

You can use the transformation functions described above to pass information throughout the life cycle of the observer. If the observer is not monitoring the returned LiveData object, the calculation of the transform function will not be performed. Because transformations are lazily evaluated, lifecycle dependent behaviors are also implicitly passed without any explicit calls or dependencies.

If you need a LifeCycle object in a ViewModel object, transformation may be a better solution. For example, if you have a UI component that accepts an address and returns its zip code, you might implement a ViewModel for that component that takes obligations like this:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // Don't do that ⬇️
       returnrepository.getPostCode(address); }}Copy the code

If you do this, the UI component will need to unsubscribe from the previous LiveData each time it calls getPostalCode() and then register the subscription for the new instance. To make matters worse, if the UI component is rebuilt, it will trigger a new call to repository.getPostcode () instead of taking advantage of the results of the previous call.

Instead, you should implement the zip code query as a transformation from the address input to LiveData, as shown in the following example:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) { addressInput.setValue(address); }}Copy the code

In the example above, the postalCode field never changes, so it is public and final; The postalCode field is defined as a transformation of addressInput, which means that the repository.getPostCode() method will be called when addressInput changes. Of course, this is only the case with observers; if there is no active observer at the time of the repository.getPostcode () call, the calculation will not be performed until the observer is added.

This mechanism allows low-level parts of the application to create lazy-evaluated LiveData objects. A ViewModel object can easily get a reference to a LiveData object, and then define transformations to it on that basis.

Create a new transform

todo

Your application may require a bunch of different transformations, but they are not provided by default. To implement your own transformation, you can use the MediatorLiveData class, which listens for other LiveData objects and handles the events they emit. MediatorLiveData correctly passes its state to the source LiveData object. To learn more about this pattern, see the documentation here.

MediatorLiveData

MediatorLiveData is a subclass of LiveData that allows you to merge multiple LiveData sources. The observer of the MediatorLiveData object is notified when any LiveData source object changes.

For example, if you have a LiveData object in the UI that can be updated by a local database or network, you can add the following data sources to the MediatorLiveData object:

  • One associated with data in a databaseLiveDataobject
  • One associated with data retrieved from the networkLiveDataobject

Your activity only needs to observe the MediatorLiveData object to receive updates from both data sources. For a more detailed example, see appendix: Show Network State in the Application Architecture guide.

todo

For more on using LiveData with Snackbar information, navigational events, and so on, see this blog.

reference

Official documentation: Overview of LiveData

Jetpack source code parsing – LiveData usage and how it works

Android LiveData source code anatomy

Android source code parsing -LiveData