LiveData overview

LiveData is an observable data store class. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the lifecycle of other application components such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state. If an Observer (represented by an Observer class) is in a STARTED or RESUMED state during its lifecycle, LiveData considers the Observer to be active. LiveData only notifies active observers of updates. Inactive observers registered to observe LiveData objects do not receive notification of changes. You can register observers that are paired with objects that implement the LifecycleOwner interface. With this relationship Lifecycle objects can be removed when the state of the corresponding Lifecycle object changes to DESTROYED. This is especially useful for activities and fragments because they can safely observe LiveData objects without worrying about leaks (the system unsubscribe them immediately when the Activity and Fragment’s life cycle is destroyed).

The advantage of LiveData

  • Ensure that the interface conforms to data state LiveData follows observer mode. LiveData notifies the Observer when the lifecycle state changes. You can integrate code to update the interface in these observers. Instead of updating the interface every time the application data changes, the observer can update the interface every time it changes.
  • An observer that does not have a memory leak will bind to Lifecycle objects and clean up after its associated Lifecycle has been destroyed.
  • If the observer’s life cycle is inactive (such as returning an Activity in the stack), it will not receive any LiveData events.
  • You no longer need to manually handle life cycle interface components and simply observe the relevant data without stopping or resuming the observation. LiveData automatically manages all of these operations because it can sense the associated lifecycle state changes as it observes.
  • Data is always up to date. If the life cycle becomes inactive, it receives the latest data when it becomes active again. For example, an Activity that was once in the background receives the latest data as soon as it returns to the foreground
  • Appropriate configuration changes If an Activity or Fragment is recreated due to a configuration change (such as a device rotation), it immediately receives the latest available data.
  • Shared resources

You can use the singleton pattern to extend LiveData objects to encapsulate system services so that they can be shared across applications. The LiveData object is connected to the system service once, and then any observer that needs the corresponding resource only needs to observe the LiveData object. For more details, see Extending LiveData.

LiveData object is used

  1. Create a LiveData instance to store some type of data. This is usually done in the ViewModel class.
  2. Create an Observer object that defines an onChanged() method that controls what happens when the data stored by the LiveData object changes. Typically, you can create an Observer object in an interface controller, such as an Activity or Fragment
  3. Attach an Observer to a LiveData object using the observe() method. The observe() method takes the LifecycleOwner object. This causes the Observer to subscribe to the LiveData object to be notified of changes. In general, you can attach an Observer object to an interface controller, such as an Activity or Fragment.
  4. You can use the observeForever(Observer) method to register observers that are not associated with a LifecycleOwner object. In this case, the observer is treated as always active, so it is always notified of changes. You can remove these observers by calling the removeObserver(Observer) method.

When you update the value stored in the LiveData object, it triggers all registered observers (as long as the attached LifecycleOwner is active).

LiveData allows interface controller observers to subscribe to updates. When the data stored in the LiveData object is changed, the interface is automatically updated to respond.

Create a LiveData object

LiveData is a wrapper container that can be used for any data, including collectionable objects such as lists. LiveData objects are typically stored in ViewModel objects and can be accessed through getter methods, as shown in the following example

    class NameViewModel : ViewModel() {

        // Create a LiveData with a String
        val currentName: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }

        // Rest of the ViewModel...
    }
Copy the code

Initially, the data in the LiveData object is not set. Make sure that the LiveData object used to update the interface is stored in a ViewModel object, not in an Activity or Fragment, for the following reasons:

  • Avoid activities and fragments that are too large. Now, these interface controllers are responsible for displaying the data, but not for storing the data state.
  • Separate the LiveData instance from a specific Activity or Fragment instance and keep the LiveData object alive after configuration changes.

Observe the LiveData object

In most cases, the onCreate() method of the application component is the right place to start looking at the LiveData object for the following reasons:

  • Make sure the system does not make redundant calls from the Activity or Fragment’s onResume() method.
  • Make sure the Activity or Fragment has data that can be displayed immediately after it becomes active. Once the application component is STARTED, it receives the latest value from the LiveData object it is observing. This only happens if you set up the LiveData object to observe.

In general, LiveData only sends updates when the data changes, and only to active observers. An exception to this behavior is when the observer changes from inactive to active and also receives updates. In addition, if an observer changes from inactive to active a second time, it will receive updates only if the value has changed since it last became active.

How to start observing LiveData objects:

    class NameActivity : AppCompatActivity() {

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        private val model: NameViewModel by viewModels()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            // Other code to setup the activity...

            // Create the observer which updates the UI.
            val nameObserver = Observer<String> { newName ->
                // Update the UI, in this case, a TextView.
                nameTextView.text = newName
            }

            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.currentName.observe(this, nameObserver)
        }
    }
    
Copy the code

OnChanged () is immediately called to provide the latest value stored in mCurrentName after observing () in the case of passing the nameObserver parameter. If the LiveData object has not already been 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 you must use if you need to modify values stored in LiveData objects. Normally MutableLiveData is used in the ViewModel, and the ViewModel only exposes immutable LiveData objects to the observer. After setting the observer relationship, you can update the value of the LiveData object (as shown in the following example) so that all observers are triggered when a user presses a button

Button. SetOnClickListener {val anotherName = "John Doe" model. CurrentName. SetValue (anotherName)} ` ` in this case call setValue (T) Causes the observer to call its onChanged() method with the value John Doe. The button pressing method is demonstrated in this example, but the mName can also be updated by calling setValue() or postValue() for a variety of reasons, including in response to a network request or the completion of a database load. In all cases, calls to setValue() or postValue() trigger the observer and update the interface. ** You must call the setValue(T) method to update the LiveData object from the main thread. If you execute code in the worker thread, you can update the LiveData object using the postValue(T) method instead. If an observer's life cycle is STARTED or RESUMED, LiveData considers that observer to be active. The following code shows how to extend the LiveData class: ' 's class StockLiveData(Symbol: String) : LiveData<BigDecimal>() { private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } }Copy the code

The price listener implementation in this example includes the following important methods:

  • The onActive() method is called when the LiveData object has an active observer. This means that you need to start looking at stock price updates from this method.
  • The onInactive() method is called when the LiveData object has no active observers. Since no observer is listening, there is no reason to stay connected to the StockManager service.
  • The setValue(T) method updates the value of the LiveData instance and notifies any active observer of the change.

You can use the StockLiveData class, as follows:

    public class MyFragment : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val myPriceListener: LiveData<BigDecimal> = ...
            myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
                // Update the UI.
            })
        }
    }
   
Copy the code

The observe() method passes the LifecycleOwner associated with the Fragment view as the first argument. Doing this 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.
  • Once Lifecycle object is destroyed, the observer is automatically removed.

The fact that LiveData objects have lifecycle awareness means that you can share these objects across multiple activities, fragments, and services. To keep the example simple, you can implement the LiveData class as a single instance, as follows:

    class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
        private val stockManager: StockManager = StockManager(symbol)

        private val listener = { price: BigDecimal ->
            value = price
        }

        override fun onActive() {
            stockManager.requestPriceUpdates(listener)
        }

        override fun onInactive() {
            stockManager.removeUpdates(listener)
        }

        companion object {
            private lateinit var sInstance: StockLiveData

            @MainThread
            fun get(symbol: String): StockLiveData {
                sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
                return sInstance
            }
        }
    }
    
Copy the code

And you can use it in the Fragment as follows:

    class MyFragment : Fragment() {

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
                // Update the UI.
            })

        }
    
Copy the code

MyPriceListener instances can be observed with multiple Fragments and activities. LiveData connects to one or more system services only when they are visible and active.

Convert LiveData

You may want to make changes to the values stored in the LiveData object before dispatching it to an observer, or you may need to return a different LiveData instance based on the value of another instance. Lifecycle packages provide classes of mineralizations that contain helper program methods that can handle these situations. Mineral.map (): Applies functions to values stored in the LiveData object and propagates the results downstream.

    val userLiveData: LiveData<User> = UserLiveData()
    val userName: LiveData<String> = Transformations.map(userLiveData) {
        user -> "${user.name} ${user.lastName}"
    }
Copy the code

Transformations. SwitchMap () : similar to map (), the value is stored in LiveData object in the application to function, and the results will unlock and dispatch to the downstream. The function passed to switchMap() must return a LiveData object, as shown in the following example:

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

You can use the transformation method to transmit information over the lifetime of the observer. The conversion is not evaluated unless the observer is observing the returned LiveData object. Because transformations are calculated in a deferred manner, lifecycle related behaviors are implicitly passed on without the need for additional explicit calls or dependencies.

If you think Lifecycle objects need to be in the ViewModel object, a transformation might be a better solution. For example, suppose you have an interface component that accepts an address and returns a zip code for that address. You can implement a simple ViewModel for this component, as shown in the following sample code:

    class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

        private fun getPostalCode(address: String): LiveData<String> {
            // DON'T DO THIS
            return repository.getPostCode(address)
        }
    }
    
Copy the code

The interface component needs to unregister the previous LiveData object and register a new instance each time getPostalCode() is called. In addition, if the interface component is recreated, it triggers another call to the repository.getPostcode () method instead of using the results of the previous call.

You can also implement a zip code query as a translation of address input, as shown in the following example:

 class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
        private val addressInput = MutableLiveData<String>()
        val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
                address -> repository.getPostCode(address) }

        private fun setInput(address: String) {
            addressInput.value = address
        }
    }
Copy the code
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private val addressInput = MutableLiveData<String>() val postalCode: LiveData<String> = Transformations.switchMap(addressInput) { address -> repository.getPostCode(address) } private fun SetInput (address: String) {addressInput. Value = address}} In this case, the postalCode field is defined as the translation of addressInput. As long as your application has an active observer associated with the postalCode field, the value of that field is recalculated and retrieved when addressInput changes.Copy the code

Creating a new transformation

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 on other LiveData objects and handles the events they emit. MediatorLiveData correctly propagates its state to the source LiveData object. To learn more about this pattern, see the reference documentation for the Baskets.

Merge multiple LiveData sources

MediatorLiveData is a subclass of LiveData that allows you to merge multiple LiveData sources. Any change to the original LiveData source object triggers the observer of the MediatorLiveData object.

For example, if the interface has LiveData objects 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 simply observes the MediatorLiveData object to receive updates from both sources. For detailed examples, see the Appendix to the Application Architecture Guide: Expose Network State section.