architecture-samples icon indicating copy to clipboard operation
architecture-samples copied to clipboard

[master][suggestion] Boilerplate code with LiveData accessing modifiers

Open GensaGames opened this issue 6 years ago • 1 comments

Doing such duplicates for hiding external modifications just looks wrong. Is anything we can do with it?

    private val _empty = MutableLiveData<Boolean>()
    val empty: LiveData<Boolean> = _empty

We understand, it's should be clear for all, where this Mutable LiveData should not be visible from outside. However, as mentioned in the README page, it's should be as reference for developers. And it's not a reference for usual project, where this boilerplate code may be avoided somehow, with wrapper on reading only value, or something else like invoking to do mutable.

abstract class BaseViewModel : ViewModel() {

    operator fun <T> LiveData<T>.invoke() : MutableLiveData<T> {
        return this as MutableLiveData<T>
    }

}
// ...
val empty : LiveData<Boolean> = MutableLiveData<Boolean>()

// ...
// To make visible, just invoke variable. Works only inside BaseViewModel
empty().value = tasks.isNullOrEmpty()

To keep it, as pure valuable reference for a new project, could we try to do something with it? Regards,

GensaGames avatar Oct 09 '19 17:10 GensaGames

You're absolutely right to highlight the boilerplate of exposing immutable LiveData while keeping MutableLiveData private in ViewModels. This pattern:

private val _empty = MutableLiveData<Boolean>() val empty: LiveData<Boolean> = _empty is recommended for encapsulation, but it does feel repetitive, especially in larger ViewModels with many state fields.

Why the Boilerplate Exists It protects state:

ViewModel has write access (_empty)

UI has read-only access (empty)

So it prevents bugs from external mutation.

Suggestions to Reduce Boilerplate

  1. Use a Custom Property Delegate Create a delegate that handles both _xyz and xyz naming automatically:

class LiveDataDelegate<T> { private val _data = MutableLiveData<T>() val public: LiveData<T> get() = _data operator fun invoke(): MutableLiveData<T> = _data }

class MyViewModel : ViewModel() { val empty = LiveDataDelegate<Boolean>()

fun checkTasksEmpty(tasks: List<String>) {
    empty().value = tasks.isEmpty()  // writing to MutableLiveData
}

fun observeEmpty(): LiveData<Boolean> = empty.public  // exposing immutable

} 2. Extension on LiveData for Internal Casting As you mentioned:

operator fun <T> LiveData<T>.invoke(): MutableLiveData<T> = this as MutableLiveData<T> This can be unsafe, as casting LiveData to MutableLiveData externally may break encapsulation rules.

Only use this if you're strictly controlling the usage internally within a base class.

  1. Kotlin Backing Property (Cleaner but Still Explicit)

private val _empty = MutableLiveData<Boolean>() val empty: LiveData<Boolean> get() = _empty Still best for clarity. The verbosity is a trade-off for safety and readability.

Conclusion The current recommended pattern is boilerplate but intentional for safety.

However, for internal dev efficiency:

Use a wrapper class or delegate to encapsulate the pairing

Avoid dangerous as casts unless you’re in full control (internal base class)

Kotlin Compose + StateFlow (which supports .asStateFlow()) can avoid some of this in newer projects

VaradGupta23 avatar Jul 21 '25 06:07 VaradGupta23