Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Does Android LiveData get() syntax work?

I understand the need for creating getter and setter points for LiveData in the ViewModel, but I'm looking to understand how the get() syntax works in Android.

ie:

val isRealtime: LiveData<Boolean>
    get() = _isRealtime
private val _isRealtime = MutableLiveData<Boolean>()
like image 771
Adam Hurwitz Avatar asked Mar 30 '19 14:03

Adam Hurwitz


People also ask

How does LiveData work?

LiveData follows the observer pattern. LiveData notifies Observer objects when underlying data changes. You can consolidate your code to update the UI in these Observer objects. That way, you don't need to update the UI every time the app data changes because the observer does it for you.

Can I use LiveData without ViewModel?

To answer your question, No, It is not mandatory to use LiveData always inside ViewModel, it is just an observable pattern to inform the caller about updates in data. If you have something which won't be changed frequently and can be accessed by its instance. You can completely ignore wrapping it inside LiveData.

Is LiveData going to be deprecated?

This function is deprecated. The observer will only receive events if the owner is in Lifecycle. State. STARTED or Lifecycle. State.

What is difference between LiveData and MutableLiveData?

By using LiveData we can only observe the data and cannot set the data. MutableLiveData is mutable and is a subclass of LiveData. In MutableLiveData we can observe and set the values using postValue() and setValue() methods (the former being thread-safe) so that we can dispatch values to any live or active observers.


3 Answers

get() is not related to Android.

val isRealtime: LiveData<Boolean>
    get() = _isRealtime

Here, get() is overriding the automatically-generated Kotlin getter function for the isRealtime property. So, instead of returning its own value, it returns the value of _isRealtime.

Personally, I recommend simpler syntax:

private val _isRealtime = MutableLiveData<Boolean>()
val isRealtime: LiveData<Boolean> = _isRealtime

The objective of either of these is to keep the mutability private, so consumers of this class do not accidentally update the MutableLiveData themselves.

like image 175
CommonsWare Avatar answered Oct 20 '22 19:10

CommonsWare


In Kotlin we have multiple ways of exposing live data from ViewModel to the view.

class MyViewModel: ViewModel() {

    // Solution 1 - make MutableLiveData public
    // This approach works, but this is a bad idea because
    // view can modify the LiveData values
    val liveDataA1 = MutableLiveData<State>()

    // Solution 2 - let's make LiveData public (expose it instead of MutableLiveData)
    // Now from view perspective this solution looks fine, bu we have a problem,
    // because we need MutableLiveData within ViewModel to put/post new values to
    // the stream (we can't post values to LiveData).
    val liveDataA2 = MutableLiveData<State>() as LiveData<State>

    // Let's capture our requirements:
    // 1. We need to expose (immutable) LiveData to the view,
    // so it cannot edit the data itself.
    // 2. We need to access MutableLiveData from ViewModel to put/post new values.
    // Now, let's consider few appropriate solutions

    // Solution 3
    // Let's name mutable live data using underscore prefix
    private val _liveData3 = MutableLiveData<State>()
    val liveData3 = _liveData3 as LiveData<State>

    // Solution 4
    // We can also perform casting by specifying type for a variable
    // (we can do it because MutableLiveData extends LiveData)
    private val _liveData4 = MutableLiveData<State>()
    val liveData4: LiveData<State> = _liveData4

    // Solution 5
    // Starting from Kotlin 1.4-M.2 we can delegate call to another property
    private val _liveData5 = MutableLiveData<State>()
    val liveData5 by this::_liveData5

    // Solution 6
    // These above solutions work quite well, but we could do even better by
    // defining custom asLiveData extension function.
    private val _liveData6 = MutableLiveData<State>()
    val liveData6 = _liveData6.asLiveData()

    fun <T> MutableLiveData<T>.asLiveData() = this as LiveData<T>
    // Amount of code is similar, but notice that this approach works much better
    // with code completion.

    // Solution 7 (IMO Best)
    // We can also use alternative naming convention - use "mutableLiveData"
    // as variable for mutable live data instead of using underscore prefix
    private val mutableLiveData7 = MutableLiveData<State>()
    val liveData7 = mutableLiveData7.asLiveData()

    // BTW
    // We could also expose getLiveData8() method, but liveData is a state not an action.

    // Solution 9
    // This does not create backing field for the property
    // (more optimised but still Solution 7 is easier to use)
    private val _liveData9 = MutableLiveData<State>()
    val liveData9 get() = _liveData9 as LiveData<State>
}
like image 45
Igor Avatar answered Oct 20 '22 17:10

Igor


I wrote a util function for this logic:

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import kotlin.reflect.KProperty

fun <T> immutable(data: MutableLiveData<T>): Immutable<T> {
    return Immutable(data)
}

class Immutable<T>(private val data: MutableLiveData<T>) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): LiveData<T> {
        return data
    }
}

Then you can use in any of your ViewModel as:

private val _counter: MutableLiveData<Int> = MutableLiveData()    
val counter: LiveData<Int> by immutable(_counter)

or in short:

private val _counter = MutableLiveData<Int>()    
val counter by immutable(_counter)
like image 20
Frank Nguyen Avatar answered Oct 20 '22 17:10

Frank Nguyen