Livedata overview
LiveData is an observable data storage class. Unlike conventional observable classes, LiveData has life cycle awareness
If the life cycle of an observer (represented by the Observer class) is in the STARTED or RESUMED state, LiveData will assume that the observer is active.. LiveData only notifies active observers of updates. Inactive observers registered to observe LiveData objects are not notified of changes.
You can register observers paired with objects that implement the Lifecycle owner interface. With this relationship, the observer can be removed when the state of the corresponding Lifecycle object changes to DESTROYED. This is particularly useful for activities and fragments because they can safely observe LiveData objects without worrying about disclosure
LiveData benefits
- Data match page status
- No memory leaks
- No crash due to activity stop
- You no longer need to manually process the lifecycle
- Data is always up to date
- Can be used for resource sharing
Livedata usage
Generally speaking, we will create a Livedata object in the ViewModel, and then register Livedata listening in onCreate of Activity/Fragment (because there may be redundant calls for listening in onStart and onResume)
Livedata is easy to use
Still use our countdown example to start a 2000s countdown in Viewmodel, and then update the interface to the Activity through Livedata callback. The code is as follows:
- viewmodel code
class CountDownModel : ViewModel() { val countDownLivedata = MutableLiveData<String>() private var remainSecond = 2000//Seconds remaining init { val countDown = object : CountDownTimer(2000 * 1000, 1000) { override fun onTick(millisUntilFinished: Long) { remainSecond-- countDownLivedata.postValue("surplus: ${remainSecond} second") } override fun onFinish() { countDownLivedata.postValue("The countdown is over") } } countDown.start() } }
- Update ui code by observing data in activity
val countDownModel: CountDownModel by viewModels<CountDownModel> { ViewModelProvider.NewInstanceFactory() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_count_down) countDownModel.countDownLivedata.observe(this, object : Observer<String> { override fun onChanged(value: String?) { value?.let { tv_countdown_remainsecond.text = it } } }) }
Use global Livedata to listen for status in multiple views
The demo effect of this example is to create a global countdown, and then add two buttons in the Activity. After clicking, you can switch between FragmentA and FragmentB. Then we implement data monitoring through the global custom LiveData singleton. After switching the Fragment, the remaining seconds of the countdown will be displayed on the Fragment page
code:
- Global custom Livedata code
class GlobalLivedata : LiveData<String>() { val coundManager = CountDownManager() val listener = object : OnDataChangeListener { override fun change(data: String) { postValue(data) } } override fun onActive() { super.onActive() coundManager.setListener(listener) } override fun onInactive() { super.onInactive() coundManager.removeListener(listener) } companion object { private lateinit var globalData: GlobalLivedata fun getInstance(): GlobalLivedata { globalData = if (::globalData.isInitialized) globalData else GlobalLivedata() return globalData } } }
- The countdown code is long and only a part is pasted. If you are interested, you can check the complete code in github
private val listeners = mutableListOf<OnDataChangeListener>() init { val countDown = object : CountDownTimer(2000 * 1000, 1000) { override fun onTick(millisUntilFinished: Long) { remainSecond-- callback("surplus: ${remainSecond} second") } override fun onFinish() { callback("The countdown is over") } } countDown.start() } /** * Loop through callback messages */ private fun callback(msg:String) { for (listener in listeners){ listener.change(msg) } }
- Listen for countdown status in FragmentA and FragmentB
GlobalLivedata.getInstance().observe(viewLifecycleOwner, { t -> inflate.findViewById<TextView>(R.id.tv_fragmentA).text = "fragmenta: ${t}" })
GlobalLivedata.getInstance().observe(viewLifecycleOwner, { t -> inflate.findViewById<TextView>(R.id.tv_fragmentB).text = "fragmentb: ${t}" })
Convert Livedata. map and switchMap can convert existing Livedata to get new Livedata
Transformation.map
Observe the data update in the ViewModel in the activity. When you click the button in the activity, the viewmodel.sendData method will be called to send the data, and then the sent data will be converted to the activity, and then the activity will print the log display
Just look at the code:
- Create a viewmodel and create Livedata in the model
class TransMapViewModel: ViewModel() { fun sendData() { userLivedata.value=User("Li Bai",1200)//Copy userLivedata } val userLivedata =MutableLiveData<User>() val mapLiveData = Transformations.map(userLivedata){ "${it.name} : ${it.age}"//Any type of data can be returned here } } data class User(var name:String,var age:Int)
mapLiveData in the code is obtained by converting userLivedata, so when we call sendData method to update the method in userLivedata, the callback of mapLiveData will also be triggered
- Observe mapLiveData in the activity and click the button to send small data
mapViewModel.mapLiveData.observe(this,{ logEE(it) tv_map.text=it }) btn_map.setOnClickListener { mapViewModel.sendData() }
Transformation.switchMap
In this example, we implement the following logic:
Observe the data update in the ViewModel in the activity. When you click the button in the activity, the viewmodel.sendData method will be called to send the data, and then the sent data will be converted to the activity, and then the activity will print the log display
- Code in viewmodel
class SwitchMapViewModel : ViewModel() { fun sendData() { userLivedata.value = SwitchUser("Li Bai", 1200) } private val userLivedata = MutableLiveData<SwitchUser>() val mapLiveData = Transformations.switchMap(userLivedata) { changeUser(it!!) } private fun changeUser(it: SwitchUser): LiveData<String> { return MutableLiveData("${it.name} Du Fu knows your name") } } data class SwitchUser(var name: String, var age: Int)
- Call part of the code
model.mapLiveData.observe(this, { logEE(it) }) btn_switchmap.setOnClickListener { model.sendData() } Copy code
Merge two Livedata (MediatorLiveData)
Imagine a scenario where your app has the function of a comment list, which can like the contents of the list. Every like is an asynchronous error. Your product needs do not want users to like too much. For example, the number of likes can not exceed 10 times a minute. This scenario is very suitable for using the merging function of Livedata
We won't simulate such a complex scene. Our example does this:
There are two buttons on the interface. One click is equivalent to one like. We click the button ten times to display text on the interface, indicating that the user has clicked the data ten times.
Code display:
1.model code
class MeditorLiveViewModel : ViewModel() { var count =0//Count field fun setData1(name: String) { liveData1.value = name } fun setData2(age: Int) { liveData2.value = age } private val liveData1 = MutableLiveData<String>() private val liveData2 = MutableLiveData<Int>() val liveCombind = MediatorLiveData<String>() init { liveCombind.addSource(liveData1) { increase() } liveCombind.addSource(liveData2) { increase() } } private fun increase() { count++ if(count==10){ liveCombind.value="Android classmate, you have clicked ${count}I won't play with you again. Stop..." } } }
Three Livedata are created in the model, two of which are livedata1 and livedata2, corresponding to two of the buttons.
There is also a scenario where livecombine is used to callback more than ten calls
The livecombine.addsource call in the init method is used to intercept the data updates of livedata1 and livedata2 in the middle, handle the count accumulation and whether to call back livecombine
- Code in activity
bserveForevermodel.liveCombind.observe(this){ logEE(it) tv_count.text=it } btn_livedata1.setOnClickListener { model.setData1("Li Bai") } btn_livedata2.setOnClickListener { model.setData2(1000) }
The observeForever method is also a method to register Livedata listening, which means that even if the page should be overwritten and inactive, it can receive a callback for data change
Livedata and collaboration are used together
Use in emit mode
- Introduce dependency. Sometimes you may need to handle asynchronous tasks, and refresh the ui after the task is processed
This situation can be implemented using Livedata's extender
In this example, we implement the following logic:
Block 4s in the viewmodel and notify the activity
code:
- Introducing dependent plug-ins
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
- Open asynchronous task method
/** * Open asynchronous task */ fun startAsyncWithSecond(second: Int): LiveData<String> = liveData<String> { delay(second * 1000L) emit("The countdown is over")//Used to trigger data callback }
When we call the startAsyncWithSecond method, a Livedata object will be returned immediately for us to register and listen
- Register livedata listener in activity
emitSource usemodel.startAsyncWithSecond(3).observe(this){ logEE(it)//The data will be returned here after delay3s in the model }
The effect of using emitSource is the same as that of MediatorLiveData
In this example, we achieve the following effects:
Click the button to start a 3s asynchronous task, and then notify the activity to print the log.
Then start a 3s asynchronous task again. After that, notify the activity to print the log again
code:
- Create asynchronous task method
fun startAsyncEmitSource(second: Int)= liveData<String> { delay(second * 1000L) emit("${second} Second blocking complete,I'll let you know in three seconds") val emitSourceLivedata = MutableLiveData<String>() emitSource( emitSourceLivedata ) delay(second*1000L) emitSourceLivedata.value="Re blocking ${second}Seconds complete" }
- Register listener in activity
model.startAsyncEmitSource(3).observe(this){ logEE(it) }