diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModel.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModel.kt index 28f554ba3..0df7c0b3a 100644 --- a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModel.kt +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModel.kt @@ -35,6 +35,8 @@ abstract class BaseViewModel( private val _effect = MutableSharedFlow() override val effect: SharedFlow = _effect.asSharedFlow() + private val handledOneTimeEvents = mutableSetOf() + /** * Updates the [STATE] of the ViewModel. * @@ -54,4 +56,20 @@ abstract class BaseViewModel( _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() + } + } } diff --git a/core/ui/compose/common/src/test/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModelTest.kt b/core/ui/compose/common/src/test/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModelTest.kt index 6f9ba2263..a185f0acb 100644 --- a/core/ui/compose/common/src/test/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModelTest.kt +++ b/core/ui/compose/common/src/test/kotlin/app/k9mail/core/ui/compose/common/mvi/BaseViewModelTest.kt @@ -4,6 +4,8 @@ import app.cash.turbine.test import app.k9mail.core.ui.compose.testing.MainDispatcherRule import assertk.assertThat import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isTrue import kotlinx.coroutines.test.runTest import org.junit.Rule 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("Initial state") { override fun event(event: String) { updateState { event } emitEffect(event) } + + fun callHandleOneTimeEvent(event: String, block: () -> Unit) { + handleOneTimeEvent(event, block) + } } } diff --git a/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/ui/server/settings/save/BaseSaveServerSettingsViewModel.kt b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/ui/server/settings/save/BaseSaveServerSettingsViewModel.kt index 8353ea5cd..5ef3ec5ed 100644 --- a/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/ui/server/settings/save/BaseSaveServerSettingsViewModel.kt +++ b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/ui/server/settings/save/BaseSaveServerSettingsViewModel.kt @@ -24,7 +24,7 @@ abstract class BaseSaveServerSettingsViewModel( override fun event(event: Event) { when (event) { - Event.SaveServerSettings -> onSaveServerSettings() + Event.SaveServerSettings -> handleOneTimeEvent(event, ::onSaveServerSettings) Event.OnNextClicked -> navigateNext() Event.OnBackClicked -> navigateBack() } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt index cc6e084d2..8f5591000 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt @@ -23,7 +23,7 @@ open class IncomingServerSettingsViewModel( @Suppress("CyclomaticComplexMethod") override fun event(event: Event) { when (event) { - Event.LoadAccountState -> loadAccountState() + Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState) is Event.ProtocolTypeChanged -> updateProtocolType(event.protocolType) is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt index 5e3ef856a..527e2d0a4 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt @@ -21,7 +21,7 @@ open class OutgoingServerSettingsViewModel( override fun event(event: 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.SecurityChanged -> updateSecurity(event.security) diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt index 57fe86116..31b66a2bb 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt @@ -37,7 +37,7 @@ abstract class BaseServerValidationViewModel( override fun event(event: Event) { when (event) { - Event.LoadAccountStateAndValidate -> loadAccountStateAndValidate() + Event.LoadAccountStateAndValidate -> handleOneTimeEvent(event, ::loadAccountStateAndValidate) is Event.OnOAuthResult -> onOAuthResult(event.result) Event.ValidateServerSettings -> onValidateConfig() Event.OnNextClicked -> navigateNext() diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsViewModel.kt index e255e17db..7d02b09a1 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsViewModel.kt @@ -20,7 +20,7 @@ internal class AccountOptionsViewModel( override fun event(event: Event) { when (event) { - Event.LoadAccountState -> loadAccountState() + Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState) is Event.OnAccountNameChanged -> updateState { state -> state.copy(