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:
- when
LiveData
When there is an active observer, theonActive
Method is called. This means that you need to start stock price monitoring in this method. - when
LiveData
In the absence of any active observer, theonInactive
Method is called. Since no observer is listening, you don’t have toStockManager
Continue the connection. setValue(T)
Methods the updateLiveData
Value 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:
- if
Lifecycle
The object is not active, so the observer will not be notified if the data changes. - when
Lifecycle
After 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 database
LiveData
object - One associated with data retrieved from the network
LiveData
object
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