-
-
Notifications
You must be signed in to change notification settings - Fork 6
Why Mutekt?
Let's understand the reason for which Mutekt came to the picture.
Taking inspiration from Redux's way of state management having an immutable state model in the Kotlin ecosystem, implementation in Kotlin needs a lot of care and boilerplate to properly handle the state.
Assume this is the UI state model:
data class NotesState(val isLoading: Boolean, val notes: List<String>, val error: String?)
Here are well-known popular opinionated approaches in the Kotlin community to implement a reducer pattern:
In this approach, a mutable state flow is created with the initial state. Whenever a state needs to be mutated, previous the state is used to calculate the next state i.e. it just copies the previous state.
class NotesViewModel: ViewModel() {
private val _state = MutableStateFlow(NotesState(false, emptyList(), null))
val state = _state.asStateFlow()
fun loadNotes() {
_state.update { it.copy(isLoading = true) }
val notes = listOf("Lorem Ipsum")
_state.update { it.copy(notes = notes, isLoading = false) }
}
}
In this approach, the following things need to be taken care of:
- The new state should be updated atomically and with synchronization otherwise, state inconsistency will occur
(i.e.
update{}
method of StateFlow). - By dev mistake, while updating a new state if the previous state is not copied (by
it.copy()
) the previous state will be lost.
class NotesViewModel: ViewModel() {
private val isLoading = MutableStateFlow(false)
private val notes = MutableStateFlow(emptyList<String>())
private val error = MutableStateFlow<String?>(null)
val state: StateFlow<NotesState> = combine(isLoading, notes, error) { isLoading, notes, error ->
NotesState(isLoading, notes, error)
}.stateIn(viewModelScope, WhileSubscribed(), NotesState(false, emptyList(), null))
fun loadNotes() {
isLoading.value = true
notes.value = listOf("Lorem Ipsum")
isLoading.value = false
}
}
In this approach, there's no scope for mistakes but needs repeated boilerplate for each property of the state model. As new state property is added in the codebase while development, refactoring is needed every time to have proper state management.
Mutekt solves the issues around the above-mentioned approaches and lets developers focus on the state manipulation instead of declaring each and every state field every time by generating required and common boilerplate at compile time by annotation processing.
Just by inspiring from very popular Immer (from JS world), it simplifies writing immutable updates with the "mutating" syntax that helps writing clean reducer implementations.
With Mutekt you just need to declare the state model as the interface and apply the annotation. Rest magic is done by the KSP (As you already saw example earlier).
class NotesViewModel: ViewModel() {
- private val isLoading = MutableStateFlow(false)
- private val notes = MutableStateFlow(emptyList<String>())
- private val error = MutableStateFlow<String?>(null)
- val state: StateFlow<NotesState> = combine(isLoading, notes, error) { isLoading, notes, error ->
- NotesState(isLoading, notes, error)
- }.stateIn(viewModelScope, WhileSubscribed(), NotesState(false, emptyList(), null))
+ private val _state = MutableNotesState(false, emptyList(), null)
+ val state = _state.asStateFlow()
fun loadNotes() {
- isLoading.value = true
+ _state.isLoading = true
- notes.value = getNotes()
- isLoading.value = false
+ _state.apply {
+ isLoading = false
+ notes = listOf("Lorem Ipsum")
+ }
}
}
Refer to this Wiki to know what code is generated under the hood by Mutekt.