Search code examples
androidkotlinoopmutablelivedata

Access MutableLiveData Properties


I'm new to programming and starting to work through some tutorials in Kotlin for Android. Currently, I'm looking at data binding and view model design patterns. I know that operations on mutablelivedata should not be in the main activity, so I've moved them to the viewmodel.

I have some code that does the job, but wondering if there are better ways to do this. This is probably more of an OOP 101 question than specifically a kotlin question.

class MainViewModel: ViewModel() {
    val alarm : MutableLiveData<AlarmData> = MutableLiveData<AlarmData>(AlarmData(0, alarmType.HIGH))

    fun increaseValue(){
        val x = alarm.value
        var avalue = x!!.alarmValue
        var atype  = x!!.alarmType

        alarm.value = AlarmData(++avalue, atype)
    }

    fun decreaseValue(){
        val x = alarm.value
        var avalue = x!!.alarmValue
        var atype  = x!!.alarmType

        alarm.value = AlarmData(--avalue, atype)
    }

    fun togglePriority(){
        val x = alarm.value
        var avalue = x!!.alarmValue
        var atype  = x!!.alarmType

        when (atype){
            alarmType.HIGH -> alarm.value = AlarmData(avalue, alarmType.LOW)
            alarmType.MEDIUM -> alarm.value = AlarmData(avalue, alarmType.HIGH)
            alarmType.LOW -> alarm.value = AlarmData(avalue, alarmType.MEDIUM)
        }
    }

I tried the above code and it works, but seems very far from ideal- specifically that each function needs to use the same code to access the individual properties of the alarm object.


Solution

  • That is basically what you need to do, yeah - you have separate functions defining operations that are allowed, and each one needs to update the current value with a modified copy. So you will get similar code in each, handling that general read-modify-update pattern.

    (The next bit is about a general approach - what I'd actually recommend in this situation is in the last part!)

    One thing you can do in this general situation, is refactor common code into a shared (private) function, so you're not repeating it. For example, you could have a changeValue function that takes the current AlarmData and applies a change in value to it:

    private fun changeData(alarm: AlarmData, change: Int): AlarmData {
        var avalue = alarm.alarmValue
        var atype  = alarm.alarmType
    
        return AlarmData(avalue + change, atype)
    }
    
    fun increaseValue() {
        alarm.value = changeData(alarm.value!!, change = 1)
    }
    
    fun decreaseValue() {
        alarm.value = changeData(alarm.value!!, change = -1)
    }
    

    So I basically moved your duplicate code into a separate function, and made the variable part a parameter on the function. And your increase/decrease functions just call that with the appropriate value. So the duplication is cut down, and it's less to maintain too!


    But in this specific case, I wouldn't necessarily do that. You can cut your code down quite a bit:

    fun increaseValue(){
        // define the variable as non-nullable instead, so you don't need to do it when you access it
        val current = alarm.value!!
    
        // and you can just access the properties directly    
        alarm.value = AlarmData(current.alarmValue + 1, current.alarmType)
    }
    

    Or you could use a scope function with a receiver to ditch the temp variable:

    alarm.value = alarm.value!!.run { AlarmData(alarmValue + 1, alarmType) }
    

    Or if you're using another Kotlin function, data classes, you can easily copy an object while making changes to it:

    // define your data object as a data class, which gives you a copy() function
    data class AlarmData(
        val alarmValue: Int,
        val alarmType: AlarmType
    )
    
    // now you can generate modified copies easily, specifying new values for certain properties
    alarm.value = alarm.value!!.run { copy(alarmValue = alarmValue + 1) }
    
    // or if that doesn't make sense
    val current = alarm.value!!
    alarm.value = current.copy(alarmValue = current.alarmValue)
    

    So when your functions can be written in one or two lines, it might not be worth extracting a separate function - it cuts down on redundancy, but adds complexity, so it's more of a tradeoff. Your call!

    Also I'll just say that working with a LiveData that has a default value (so value can never be null) is one of the few situations where I feel like it's ok to use !! in your own code, rather than null-checking it with ? and adding a scope function like run or let. So I've only used !! in these examples because I feel like it's clearer and avoids unnecessary code for the impossible null situations - but in general it's a bad habit that leads to bugs, unless you know it's necessary. Just a warning!


    Oh one other thing, you usually wouldn't publicly expose the MutableLiveData like that - you expose a read-only LiveData property instead, like this:

    // this mutable type stays private
    private val _alarm : MutableLiveData<AlarmData> =
        MutableLiveData<AlarmData>(AlarmData(0, alarmType.HIGH))
    
    // for the public version, you cast it to the immutable LiveData supertype
    val alarm: LiveData<AlarmData> = _alarm
    // or if you want to avoid creating another backing field, use a getter
    val alarm: LiveData<AlarmData> get() = _alarm
    

    This way you force consumers to use your interface (the functions you defined) to change the data, instead of being able to poke at the LiveData's value directly