Reputation: 39539
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.
Upvotes: 18
Views: 16173
Reputation: 1153
I really liked The Lucky Coder's answer. Regarding the danger on trying to set a null value, I think we should throw an exception for this (similar to what David Whitman was pointing):
class NonNullLiveData<T>(private val defaultValue: T) : MutableLiveData<T>() {
override fun getValue(): T = super.getValue() ?: defaultValue
override fun setValue(value: T) {
if(value != null) {
super.setValue(value)
}
else throw IllegalArgumentException("Cannot set a null value to this Type. Use normal MutableLiveData instead for that.")
}
override fun postValue(value: T) {
if(value != null) {
super.postValue(value)
}
else throw IllegalArgumentException("Cannot post a null value to this Type. Use normal MutableLiveData instead for that.")
}
fun observe(owner: LifecycleOwner, body: (T) -> Unit) {
observe(owner, Observer<T> {
body(it ?: defaultValue)
})
}
}
Now the value in the MutableLiveData will never be null, so that the class would not be used in a way it's not intended.
Upvotes: 0
Reputation: 2086
When using strings is simple by using this way:
val someLiveData = MutableLiveData<String>()
...
someLiveData.value.orEmpty()
And you will get actual value if set or an empty string instead of null
.
Upvotes: 0
Reputation: 2249
This is my solution.
class NotNullLiveData<T : Any>
constructor(private val default: T) : MediatorLiveData<T>() {
override fun getValue(): T {
return super.getValue() ?: default
}
override fun setValue(value: T) {
super.setValue(value)
}
}
@MainThread
fun <T : Any> mutableLiveDataOfNotNull(initValue: T): NotNullLiveData<T> {
val liveData = NotNullLiveData(initValue)
liveData.value = initValue
return liveData
}
@MainThread
fun <T> mutableLiveDataOf(initValue: T): MutableLiveData<T> {
val liveData = MutableLiveData<T>()
liveData.value = initValue
return liveData
}
fun <T : Any> LiveData<T?>.toNotNull(default: T): NotNullLiveData<T> {
val result = NotNullLiveData(default)
result.addSource(this) {
result.value = it ?: default
}
return result
}
Upvotes: 0
Reputation: 1104
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
Upvotes: 2
Reputation: 1354
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) { ... }
Upvotes: 4
Reputation: 596
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
}
Upvotes: 9
Reputation: 279
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)
}
}
Upvotes: 13
Reputation: 378
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 :)
Upvotes: -3