Add BaseViewModel to abstract state and effect setup

This commit is contained in:
Wolf-Martell Montwé 2023-05-11 11:46:59 +02:00
parent 1d93ffb89f
commit 2b2745ba9c
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
2 changed files with 113 additions and 0 deletions

View file

@ -0,0 +1,57 @@
package app.k9mail.core.ui.compose.common.mvi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* An abstract base ViewModel that implements [UnidirectionalViewModel] and provides basic
* functionality for managing state and effects.
*
* @param STATE The type that represents the state of the ViewModel. For example, the UI state of a screen can be
* represented as a state.
* @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. For
* example, a button click can be represented as an event.
* @param EFFECT The type that represents side-effects that can occur in response to the state changes. For example,
* a navigation event can be represented as an effect.
*
* @property initialState The initial [STATE] of the ViewModel.
*/
abstract class BaseViewModel<STATE, EVENT, EFFECT>(
initialState: STATE,
) : ViewModel(),
UnidirectionalViewModel<STATE, EVENT, EFFECT> {
private val _state = MutableStateFlow(initialState)
override val state: StateFlow<STATE> = _state.asStateFlow()
private val _effect = MutableSharedFlow<EFFECT>()
override val effect: SharedFlow<EFFECT> = _effect.asSharedFlow()
/**
* Updates the [STATE] of the ViewModel.
*
* @param update A function that takes the current [STATE] and produces a new [STATE].
*/
protected fun updateState(update: (STATE) -> STATE) {
_state.update(update)
}
/**
* Emits a side effect.
*
* @param effect The [EFFECT] to emit.
*/
protected fun emitEffect(effect: EFFECT) {
viewModelScope.launch {
_effect.emit(effect)
}
}
}

View file

@ -0,0 +1,56 @@
package app.k9mail.core.ui.compose.common.mvi
import app.cash.turbine.test
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
import assertk.assertThat
import assertk.assertions.isEqualTo
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class BaseViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `should emit initial state`() = runTest {
val viewModel = TestBaseViewModel()
assertThat(viewModel.state.value).isEqualTo("Initial state")
}
@Test
fun `should update state`() = runTest {
val viewModel = TestBaseViewModel()
viewModel.event("Test event")
assertThat(viewModel.state.value).isEqualTo("Test event")
viewModel.event("Another test event")
assertThat(viewModel.state.value).isEqualTo("Another test event")
}
@Test
fun `should emit effects`() = runTest {
val viewModel = TestBaseViewModel()
viewModel.effect.test {
viewModel.event("Test effect")
assertThat(awaitItem()).isEqualTo("Test effect")
viewModel.event("Another test effect")
assertThat(awaitItem()).isEqualTo("Another test effect")
}
}
private class TestBaseViewModel : BaseViewModel<String, String, String>("Initial state") {
override fun event(event: String) {
updateState { event }
emitEffect(event)
}
}
}