Merge pull request #7292 from thunderbird/one_time_initialization

Add support for one-time (initialization) events
This commit is contained in:
cketti 2023-11-04 06:12:43 -04:00 committed by GitHub
commit 0b23c20b7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 5 deletions

View file

@ -35,6 +35,8 @@ abstract class BaseViewModel<STATE, EVENT, EFFECT>(
private val _effect = MutableSharedFlow<EFFECT>() private val _effect = MutableSharedFlow<EFFECT>()
override val effect: SharedFlow<EFFECT> = _effect.asSharedFlow() override val effect: SharedFlow<EFFECT> = _effect.asSharedFlow()
private val handledOneTimeEvents = mutableSetOf<EVENT>()
/** /**
* Updates the [STATE] of the ViewModel. * Updates the [STATE] of the ViewModel.
* *
@ -54,4 +56,20 @@ abstract class BaseViewModel<STATE, EVENT, EFFECT>(
_effect.emit(effect) _effect.emit(effect)
} }
} }
/**
* Ensures that one-time events are only handled once.
*
* When you can't ensure that an event is only sent once, but you want the event to only be handled once, call this
* method. It will ensure [block] is only executed the first time this function is called. Subsequent calls with an
* [event] argument equal to that of a previous invocation will not execute [block].
*
* Multiple one-time events are supported.
*/
protected fun handleOneTimeEvent(event: EVENT, block: () -> Unit) {
if (event !in handledOneTimeEvents) {
handledOneTimeEvents.add(event)
block()
}
}
} }

View file

@ -4,6 +4,8 @@ import app.cash.turbine.test
import app.k9mail.core.ui.compose.testing.MainDispatcherRule import app.k9mail.core.ui.compose.testing.MainDispatcherRule
import assertk.assertThat import assertk.assertThat
import assertk.assertions.isEqualTo import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isTrue
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -47,10 +49,61 @@ class BaseViewModelTest {
} }
} }
@Test
fun `handleOneTimeEvent() should execute block`() = runTest {
val viewModel = TestBaseViewModel()
var eventHandled = false
viewModel.callHandleOneTimeEvent(event = "event") {
eventHandled = true
}
assertThat(eventHandled).isTrue()
}
@Test
fun `handleOneTimeEvent() should execute block only once`() = runTest {
val viewModel = TestBaseViewModel()
var eventHandledCount = 0
repeat(2) {
viewModel.callHandleOneTimeEvent(event = "event") {
eventHandledCount++
}
}
assertThat(eventHandledCount).isEqualTo(1)
}
@Test
fun `handleOneTimeEvent() should support multiple one-time events`() = runTest {
val viewModel = TestBaseViewModel()
var eventOneHandled = false
var eventTwoHandled = false
viewModel.callHandleOneTimeEvent(event = "eventOne") {
eventOneHandled = true
}
assertThat(eventOneHandled).isTrue()
assertThat(eventTwoHandled).isFalse()
viewModel.callHandleOneTimeEvent(event = "eventTwo") {
eventTwoHandled = true
}
assertThat(eventOneHandled).isTrue()
assertThat(eventTwoHandled).isTrue()
}
private class TestBaseViewModel : BaseViewModel<String, String, String>("Initial state") { private class TestBaseViewModel : BaseViewModel<String, String, String>("Initial state") {
override fun event(event: String) { override fun event(event: String) {
updateState { event } updateState { event }
emitEffect(event) emitEffect(event)
} }
fun callHandleOneTimeEvent(event: String, block: () -> Unit) {
handleOneTimeEvent(event, block)
}
} }
} }

View file

@ -24,7 +24,7 @@ abstract class BaseSaveServerSettingsViewModel(
override fun event(event: Event) { override fun event(event: Event) {
when (event) { when (event) {
Event.SaveServerSettings -> onSaveServerSettings() Event.SaveServerSettings -> handleOneTimeEvent(event, ::onSaveServerSettings)
Event.OnNextClicked -> navigateNext() Event.OnNextClicked -> navigateNext()
Event.OnBackClicked -> navigateBack() Event.OnBackClicked -> navigateBack()
} }

View file

@ -23,7 +23,7 @@ open class IncomingServerSettingsViewModel(
@Suppress("CyclomaticComplexMethod") @Suppress("CyclomaticComplexMethod")
override fun event(event: Event) { override fun event(event: Event) {
when (event) { when (event) {
Event.LoadAccountState -> loadAccountState() Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState)
is Event.ProtocolTypeChanged -> updateProtocolType(event.protocolType) is Event.ProtocolTypeChanged -> updateProtocolType(event.protocolType)
is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) } is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) }

View file

@ -21,7 +21,7 @@ open class OutgoingServerSettingsViewModel(
override fun event(event: Event) { override fun event(event: Event) {
when (event) { when (event) {
Event.LoadAccountState -> loadAccountState() Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState)
is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) } is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) }
is Event.SecurityChanged -> updateSecurity(event.security) is Event.SecurityChanged -> updateSecurity(event.security)

View file

@ -37,7 +37,7 @@ abstract class BaseServerValidationViewModel(
override fun event(event: Event) { override fun event(event: Event) {
when (event) { when (event) {
Event.LoadAccountStateAndValidate -> loadAccountStateAndValidate() Event.LoadAccountStateAndValidate -> handleOneTimeEvent(event, ::loadAccountStateAndValidate)
is Event.OnOAuthResult -> onOAuthResult(event.result) is Event.OnOAuthResult -> onOAuthResult(event.result)
Event.ValidateServerSettings -> onValidateConfig() Event.ValidateServerSettings -> onValidateConfig()
Event.OnNextClicked -> navigateNext() Event.OnNextClicked -> navigateNext()

View file

@ -20,7 +20,7 @@ internal class AccountOptionsViewModel(
override fun event(event: Event) { override fun event(event: Event) {
when (event) { when (event) {
Event.LoadAccountState -> loadAccountState() Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState)
is Event.OnAccountNameChanged -> updateState { state -> is Event.OnAccountNameChanged -> updateState { state ->
state.copy( state.copy(