Nullability and LiveData with Kotlin
Solution 1
I created an extension property. It's not super pretty, but it is pretty straightforward.
val <T> LiveData<T>.valueNN
get() = this.value!!
Usage
spinner.loading = myLiveData.valueNN.homeState.loading
I'm not sold on appending "NN" as a good naming convention, but that's beyond the scope of the question :)
Solution 2
I little improve answer The Lucky Coder. This implementation cannot accept null values at all.
class NonNullMutableLiveData<T: Any>(initValue: T): MutableLiveData<T>() {
init {
value = initValue
}
override fun getValue(): T {
return super.getValue()!!
}
override fun setValue(value: T) {
super.setValue(value)
}
fun observe(owner: LifecycleOwner, body: (T) -> Unit) {
observe(owner, Observer<T> { t -> body(t!!) })
}
override fun postValue(value: T) {
super.postValue(value)
}
}
Solution 3
I don't know if this is the best solution but this is what I came up with and what I use:
class NonNullLiveData<T>(private val defaultValue: T) : MutableLiveData<T>() {
override fun getValue(): T = super.getValue() ?: defaultValue
fun observe(owner: LifecycleOwner, body: (T) -> Unit) {
observe(owner, Observer<T> {
body(it ?: defaultValue)
})
}
}
Creating the field:
val string = NonNullLiveData("")
And observing it:
viewModel.string.observe(this) {
// Do someting with the data
}
Solution 4
You can create an extension for LifecycleOwner
fun <T> LifecycleOwner.observe(liveData: LiveData<T?>, lambda: (T) -> Unit) {
liveData.observe(this, Observer { if (it != null) lambda(it) })
}
and then in your fragment/activity
observe(liveData) { ... }
Solution 5
You can do this
normalLiveData
.nonNull()
.observe(this, { result ->
// result is non null now
})
There is an article about it. https://medium.com/@henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
Comments
-
ligi almost 2 years
I want to use LiveData with Kotlin and have values that should not be null. How do you deal with this? Perhaps a wrapper around LiveData? Searching for good patterns here .. As an example:
class NetworkDefinitionProvider : MutableLiveData<NetworkDefinition>() { val allDefinitions = mutableListOf(RinkebyNetworkDefinition(), MainnetNetworkDefinition(), RopstenNetworkDefinition()) init { value = allDefinitions.first() } fun setCurrent(value: NetworkDefinition) { setValue(value) } }
I know value will not be null when accessing - but I will always have to check for null or have these ugly !!'s around.
-
David Whitman about 6 yearsThe downside of this is that if somebody DOES put a
null
into yourNonNullLiveData
, you emit to your observers a value that's completely wrong (thedefaultValue
). Depends maybe on your situation, but I'd rather have a crash if someone puts a null into a non-null place. -
Maks almost 6 yearsThats excellent, thanks for posting this! The AS linter complained about the overridden fun's not actually doing anything, which made me think of wrapping the setValue and postValue in value.let {...} to prevent null values being set into the LiveData.
-
Admin almost 6 yearsJust playing devil's advocate, but are you absolutely sure that
getValue()
is guaranteed to return a non-null? Obviously with this implementation there's no way a client could set the value tonull
, but is there anything internally withinLiveData
that could possibly null the value? -
Admin almost 6 yearsDon't do this. Simply force-unwrapping the underlying value is dangerous because
this.value
can be null. Only when you enforce non-nullability in the setters can you safely force-unwrap like the other answers suggest. -
David Whitman almost 6 years@Weikardzaena From the OP: "I know value will not be null when accessing". Although what I did was to subclass
LiveData
and have a private innerMutableLiveData
so there is no way that any outside classes can change the value of myLiveData
tonull
. -
Anigif over 5 yearsOne of the problems with this approach is that
Observer
will allways be called right away when observed. In the standardLiveData
implementation, it won't be called untilvalue
is set the first time, but in this examplevalue
is always set right away. @Weikardzaena AFAIK that won't be the case since value is set intially