LiveData is an observable data holder class. Unlike regular Observables, LiveData is lifecycle aware, which means it respects the lifecycle of other application components, such as Activities, fragments, or services. This awareness ensures that LiveData updates only observers of application components that are in the active lifecycle state.
LiveData, as an Observer, will behave like an Observer class; it can be active if its life cycle is STARTED or RESUMED. LiveData only notifies active observers of updates. Inactive observers who register to observe LiveData objects do not receive notification of updates.
You can register an observer that is paired with an object that implements the LifecycleOwner interface. This relationship allows the observer to be removed when the corresponding Lifecycle object’s Lifecycle state changes to DESTROYED. This is particularly useful for Activities and fragments, because they can safely observe LiveData objects without worrying about leaks – activities and fragments are unsubscribed as soon as their life cycle is broken.
Advantages of using LiveData
Using LiveData has the following advantages:
Make sure your UI matches the state of your data
LiveData follows the observer pattern. LiveData notifies the object when the Observer lifecycle state changes. You can consolidate your code to update the UI in these observers. Instead of updating your UI every time your APP data is updated, your observer can update your UI every time there is a change.
No memory leaks
The observer binds the Lifecycle object and cleans it up if its associated Lifecycle is broken.
Stopping activity does not cause a crash
If the observer’s life cycle is inactive, such as an activity in the back stack, it does not receive any LiveData events.
You no longer need to handle the life cycle manually
The UI component only observes related data and does not stop or resume observation. LiveData automatically manages all of this because it knows the associated lifecycle state changes as it observes.
Always the latest data
If a life cycle becomes inactive, it will receive the latest data when it becomes active again. For example, an activity in the background receives the latest data as soon as it returns to the foreground.
Adaptive configuration changes
If an activity or fragment is recreated due to a configuration change, such as device rotation, it will immediately receive the latest available data.
Resource sharing
You can use the Singleton pattern to extend the LiveData object to wrap system services to share them in your applications. The LiveData object is connected to the system service once and can then be viewed by any observer who needs the resource.
Use LiveData objects
Follow these steps to use the LiveData object:
- 1. Create
LiveData
Instance to hold a specific type of data. This is usually inViewModelClass. - 2. Create an observer object that definesonChanged()Method, which controls
LiveData
What happens when the data held by an object changes. You typically create an observer object, such as an Activity or fragment, in a UI controller. - Use 3.observe()Method to append the observer object to
LiveData
Object.observe()
Method acceptsLifecycleOwnerObject. Subscribe to the observer objectLiveData
Object to be notified when changes occur. It is common to attach an observer object to a UI controller, such as an Activity or fragment.
Note: You can use the observeForever(Observer) method to register an observer that is not associated with a LifecycleOwner object. In this case, the observer is always considered active and is therefore always notified of modifications. You can remove these observers that call the removeObserver(Observer) method.Copy the code
When you update the values stored in the LiveData object, as long as the attached LifecycleOwner is active, it triggers all registered observers.
LiveData allows UI Controller observers to subscribe to updates. When the data held by the LiveData object changes, the UI is automatically updated.
Create a LiveData object
LiveData is a wrapper that can be used for any data, including objects that implement collections, such as lists. LiveData objects are typically stored in ViewModel objects and accessed through getter methods, as shown in the following example:
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> currentName;
public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
}
return currentName;
}
// Rest of the ViewModel...
}
Copy the code
Initially, the data in the LiveData object is not set.
Note: Make sure the LiveData object that updates the UI is stored in the ViewModel object, not in an activity or fragment, for the following reasons: 1. To avoid expansive activity and debris. These UI controllers are now responsible for displaying the data instead of saving the data state. 2. Decouple LiveData instances from specific active or fragment instances and allow LiveData objects to survive configuration changes.
Observe the LiveData object
In most cases, the onCreate() method of an application component is the appropriate place to start observing LiveData objects for the following reasons:
- To ensure that the system does not make redundant calls from the activity or fragment’s onResume() method.
- Ensure that the activity or fragment has data that can be displayed immediately when active. Once the application component is started, it looks from what it sees
LiveData
Object receives the most recent value. Only if you set up the one to observeLiveData
Object.
In general, LiveData only provides updates when the data changes and only provides updates to active observers. One exception to this behavior is when the observer changes from inactive to active, it also receives updates. In addition, if the observer changes from inactive to active a second time, the update will only be received if the value has changed since the last active.
The following sample code demonstrates how to start observing LiveData objects:
public class NameActivity extends AppCompatActivity {
private NameViewModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
model = ViewModelProviders.of(this).get(NameViewModel.class);
// Create the observer whichupdates the UI. final Observer<String> nameObserver = new Observer<String>() { @Override public void onChanged(@Nullable final String newName) { // Update the UI,in this case, a TextView. nameTextView.setText(newName); }}; // Observe the LiveData, passinginthis activity as the LifecycleOwner and the observer. model.getCurrentName().observe(this, nameObserver); }}Copy the code
OnChanged () is called immediately after invoke Observe () with the passed nameObserver argument, providing the latest value stored in mCurrentName. If the LiveData object is not set in mCurrentName, onChanged() is not called.
Update the LiveData object
LiveData does not expose available methods to update stored data. The MutableLiveData class exposes setValue(T) and postValue(T) methods, which must be used if you need to edit values stored in LiveData objects. Typically, MutableLiveData is used in the ViewModel, which then exposes immutable LiveData objects only to observers.
Once the observer relationship is set up, the value of the LiveData object can be updated, as shown in the following example, which triggers all observers when the user clicks a button:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe"; model.getCurrentName().setValue(anotherName); }});Copy the code
Calling setValue(T) in the example causes observers to call their onChanged() method with the value John Doe. This example shows a button pressed, but the mName can be updated by calling setValue() or postValue() for a number of reasons, including responding to a network request or completing a database load; In all cases, a call to setValue() or postValue() triggers the observer and updates the UI.
Note: The setValue(T) method must be called to update the LiveData object from the main thread. If the code is executing in a worker thread, the postValue(T) method can be used to update the LiveData object.
Use LiveData in conjunction with Room
The Room persistence library supports observable queries that return LiveData objects. Observable queries are written as part of a database access Object (DAO).
Room generates all the code needed to update the LiveData object when updating the database. The generated code runs the query asynchronously on background threads as needed. This pattern is useful for keeping the data displayed in the UI in sync with the data stored in the database. You can read more about Room and DAOs in the Room Persistent Library guide.
Use Coroutines with LiveData
LiveData includes support for Kotlin coroutines. For more information, see Using Kotlin Coroutines with Android Architecture Components.
Inheritance LiveData
If an observer’s life cycle is STARTED or RESUMED, LiveData considers the observer to be active. The following example code shows how to extend the LiveData class:
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager stockManager;
private SimplePriceListener listener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price); }}; public StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected voidonActive() {
stockManager.requestPriceUpdates(listener);
}
@Override
protected void onInactive() { stockManager.removeUpdates(listener); }}Copy the code
The implementation of the Price Listener in this example includes the following important methods:
- when
LiveData
Called when the object has an active observeronActive()Methods. This means that you need to start looking at stock price updates from this method. - when
LiveData
Object is called when there are no active observersonInactive()Methods. Because no observer is listening, there is no reason to stay withStockManager
Connections to services. - setValue(T)Methods the update
LiveData
Instance, and notifies any active observer of the change.
You can use the StockLiveData class as follows:
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 passes the fragment with the first argument being an instance of LifecycleOwner. Doing so means that this observer is bound to the Lifecycle object associated with the owner, which means:
- If Lifecycle object is not active, the observer will not be called even if the value changes.
- After the lifecycle object is destroyed, the observer is automatically deleted.
LiveData objects are lifecycle aware, which means you can share them across multiple activities, fragments, and services. To keep the example simple, the LiveData class can be implemented as a singleton, as follows:
public class StockLiveData extends LiveData<BigDecimal> {
private static StockLiveData sInstance;
private StockManager stockManager;
private SimplePriceListener listener = 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) {
stockManager = new StockManager(symbol);
}
@Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}
@Override
protected void onInactive() { stockManager.removeUpdates(listener); }}Copy the code
You can use it in fragments like this:
public class MyFragment extends Fragment { @Override public void onActivityCreated(Bundle savedInstanceState) { StockLiveData.get(symbol).observe(this, price -> { // Update the UI. }); }}Copy the code
Multiple fragments and activities can be observed for MyPriceListener instances. LiveData connects to system services only when one or more of them are visible and active.
Transform LiveData
You may want to change the value stored in the LiveData object before sending it to the observer, or you may need to return another LiveData instance based on the value of another LiveData instance. The lifecycle package provides classes that contain helper methods to support these scenarios.
Transformations.map()
Functions are applied to values stored in LiveData objects and the results are propagated downward.
LiveData<User> userLiveData = ... ; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name +"" + user.lastName
});
Copy the code
Transformations.switchMap()
Similar to map(), functions are applied to values stored in LiveData objects and the results are unpacked and dispatched downward. The function passed to switchMap() 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 transformation methods to carry information throughout the lifecycle of the observer. The conversion is not evaluated unless the observer is observing the returned LiveData object. Because transformations are computed lazily, lifecycle related behaviors are implicitly passed without the need for additional explicit calls or dependencies.
If you think you need a lifecycle object in a ViewModel object, a transformation might be a better solution. For example, suppose you have a UI component that takes an address and returns the zip code for that address. You can implement a simple ViewModel for this component, as shown in the sample code below:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS return repository.getPostCode(address); }}Copy the code
The UI component then needs to unregister from the previous LiveData object and register with the new instance each time getPostalCode() is called. In addition, if the UI component is recreated, it will trigger another call to the repository.getPostcode () method instead of using the results of the previous call.
Instead, you can implement a zip code query as a translation of address input, 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 this case, the postalCode field is defined as a transformation of addressInput. As long as your application has an active observer associated with the postalCode field, the value of the field is recalculated and retrieved every time addressInput changes.
This mechanism allows lower-level applications to create LiveData objects that are deferred on demand. A ViewModel object can easily get a reference to a LiveData object and then define transformation rules on it.
Create a new view
There are a dozen different specific transformations that might be useful in your application, 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 processes the events they emit. MediatorLiveData correctly propagates its state to the source LiveData object. For more information about this pattern, see the Reference documentation for the Transformation class.
Merge multiple LiveData sources
MediatorLiveData is a subclass of LiveData that allows you to merge multiple LiveData sources. Then, when any of the original LiveData source objects change, the observer of the MediatorLiveData object is triggered.
For example, if you have a LiveData object in your UI that can be updated from a local database or network, you can add the following source to the MediatorLiveData object:
- A LiveData object associated with data stored in a database.
- A LiveData object associated with data accessed from the network.
Your activity can receive updates from both sources simply by observing the MediatorLiveData object. For detailed examples, see the Network State section of the Appendix: Guide to Exposing Application Architecture.
- Android Jetpack Architecture Component family first introduction
- Android Jetpack Architecture component series 2 DataBinding
- Android Jetpack architecture component series three life cycle aware components
- Android Jetpack architecture component series four LiveData