41. An Android Jetpack LiveData Tutorial

The previous chapter began the process of designing an app to conform to the recommended Jetpack architecture guidelines. These initial steps involved the selection of the Fragment+ViewModel project template and the implementation of the data model for the app user interface within a ViewModel instance.

This chapter will further enhance the app design by making use of the LiveData architecture component. Once LiveData support has been added to the project in this chapter, the next chapters (starting with “An Overview of Android Jetpack Data Binding”) will make use of the Jetpack Data Binding library to eliminate even more code from the project.

41.1 LiveData - A Recap

LiveData was introduced previously in the chapter entitled “Modern Android App Architecture with Jetpack”. As described earlier, the LiveData component can be used as a wrapper around data values within a view model. Once contained in a LiveData instance, those variables become observable to other objects within the app, typically UI controllers such as Activities and Fragments. This allows the UI controller to receive a notification whenever the underlying LiveData value changes. An observer is set up by creating an instance of the Observer class and defining an onChange() method to be called when the LiveData value changes. Once the Observer instance has been created, it is attached to the LiveData object via a call to the LiveData object’s observe() method.

LiveData instances can be declared as being mutable using the MutableLiveData class, allowing both the ViewModel and UI controller to make changes to the underlying data value.

41.2 Adding LiveData to the ViewModel

Launch Android Studio, open the ViewModelDemo project created in the previous chapter and open the MainViewModel.kt file which should currently read as follows:

package com.ebookfrenzy.viewmodeldemo.ui.main

 

import androidx.lifecycle.ViewModel

 

class MainViewModel : ViewModel() {

 

    private val rate = 0.74f

    private var dollarText = ""

    private var result: Float = 0f

 

    fun setAmount(value: String) {

        this.dollarText = value

        result = value.toFloat() * rate

    }

 

    fun getResult(): Float {

        return result

    }

}

The objective of this stage in the chapter is to wrap the result variable in a MutableLiveData instance (the object will need to be mutable so that the value can be changed each time the user requests a currency conversion). Begin by modifying the class so that it now reads as follows noting that an additional package needs to be imported when making use of LiveData:

package com.ebookfrenzy.viewmodeldemo.ui.main

 

import androidx.lifecycle.ViewModel

import androidx.lifecycle.MutableLiveData

 

class MainViewModel : ViewModel() {

 

    private val rate = 0.74f

    private var dollarText = ""

    private var result: MutableLiveData<Float> = MutableLiveData()

 

    fun setAmount(value: String) {

        this.dollarText = value

        result = value.toFloat() * rate

    }

 

    fun getResult(): Float {

        return result

    }

}

Now that the result variable is contained in a mutable LiveData instance, both the setAmount() and getResult() methods need to be modified. In the case of the setAmount() method, a value can no longer be assigned to the result variable using the assignment (=) operator. Instead, the LiveData setValue() method must be called, passing through the new value as an argument. As currently implemented, the getResult() method is declared as returning a Float value and now needs to be changed to return a MutableLiveData object. Making these remaining changes results in the following class file:

package com.ebookfrenzy.viewmodeldemo.ui.main

 

import androidx.lifecycle.ViewModel

import androidx.lifecycle.MutableLiveData

 

class MainViewModel : ViewModel() {

 

    private val rate = 0.74f

    private var dollarText = ""

    private var result: MutableLiveData<Float> = MutableLiveData()

 

    fun setAmount(value: String) {

        this.dollarText = value

        result.setValue(value.toFloat() * rate)

    }

 

    fun getResult(): MutableLiveData<Float> {

        return result

    }

}

41.3 Implementing the Observer

Now that the conversion result is contained within a LiveData instance, the next step is to configure an observer within the UI controller which, in this example, is the MainFragment class. Locate the MainFragment.kt class (app -> java -> <package name> -> MainFragment), double-click on it to load it into the editor and modify the onActivityCreated() method to create a new Observer instance named resultObserver:

.

.

import androidx.lifecycle.Observer

.

.

override fun onActivityCreated(savedInstanceState: Bundle?) {

    super.onActivityCreated(savedInstanceState)

    viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

 

    binding.resultText.text = viewModel.getResult().toString()

 

    val resultObserver = Observer<Float> {

                           result -> binding.resultText.text = result.toString()

    }

.

.

}

The resultObserver instance declares lambda code which, when called, is passed the current result value which it then converts to a string and displays on the resultText TextView object. The next step is to add the observer to the result LiveData object, a reference to which can be obtained via a call to the getResult() method of the ViewModel object. Since updating the result TextView is now the responsibility of the onChanged() callback method, the existing lines of code to perform this task can now be deleted:

override fun onActivityCreated(savedInstanceState: Bundle?) {

    super.onActivityCreated(savedInstanceState)

    viewModel = ViewModelProvider(this).get(MainViewModel::class.java

 

    val resultObserver = Observer<Float> { result -> resultText.text =

                                                       result.toString()

    }

 

    viewModel.getResult().observe(viewLifecycleOwner, resultObserver)

 

    binding.convertButton.setOnClickListener {

 

        if (dollarText.text.isNotEmpty()) {

            viewModel.setAmount(dollarText.text.toString())

        } else {

            resultText.text = "No Value"

        }

    }

}

Compile and run the app, enter a value into the dollar field, click on the Convert button and verify that the converted euro amount appears on the TextView. This confirms that the observer received notification that the result value had changed and called the onChanged() method to display the latest data.

Note in the above implementation of the onActivityCreated() method that the line of code responsible for displaying the current result value each time the method was called was removed. This was originally put in place to ensure that the displayed value was not lost in the event that the Fragment was recreated for any reason. Because LiveData monitors the lifecycle status of its observers, this step is no longer necessary. When LiveData detects that the UI controller was recreated, it automatically triggers any associated observers and provides the latest data. Verify this by rotating the device while a euro value is displayed on the TextView object and confirming that the value is not lost.

Before moving on to the next chapter close the project, copy the ViewModelDemo project folder and save it as ViewModelDemo_LiveData so that it can be used later when looking at saving ViewModel state.

41.4 Summary

This chapter demonstrated the use of the Android LiveData component to make sure that the data displayed to the user always matches that stored in the ViewModel. This relatively simple process consisted of wrapping a ViewModel data value within a LiveData object and setting up an observer within the UI controller subscribed to the LiveData value. Each time the LiveData value changes, the observer is notified and the onChanged() method called and passed the updated value.

Adding LiveData support to the project has gone some way towards simplifying the design of the project. Additional and significant improvements are also possible by making use of the Data Binding Library, details of which will be covered in a later chapter. Before doing that, however, we will look saving ViewModel state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.238.71.155