ligi
ligi

Reputation: 39539

Nullability and LiveData with Kotlin

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

Answers (8)

Ignacio Garcia
Ignacio Garcia

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

Vladimir Petrovski
Vladimir Petrovski

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

ipcjs
ipcjs

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

Henry Tao
Henry Tao

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

Filip P.
Filip P.

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

TheLuckyCoder
TheLuckyCoder

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

icebail
icebail

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

David Whitman
David Whitman

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

Related Questions