From f709170d3392f209f9e4b18e28c30e185b1932f4 Mon Sep 17 00:00:00 2001 From: Abhi <15965088+abhimuktheeswarar@users.noreply.github.com> Date: Sat, 9 Oct 2021 18:21:30 +0530 Subject: [PATCH 1/3] Fix: Duplicate actions emitted This happen when another action is emitted from a SideEffect (or anywhere outside) before the same Action is emitted from dispatcher function, followed by ForceDistinctAction. Now, this is fixed by using merge(mutableActions, actionStates.map { it.action }).distinctUntilChanged() instead. --- .../domain/sideeffects/CounterSideEffect.kt | 8 +++++++ .../kotlin/com/msabhi/flywheel/Flywheel.kt | 22 ++++++------------- gradle.properties | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/androidApp/src/main/java/com/msabhi/androidApp/counter/domain/sideeffects/CounterSideEffect.kt b/androidApp/src/main/java/com/msabhi/androidApp/counter/domain/sideeffects/CounterSideEffect.kt index dbae9d6..5c3794a 100644 --- a/androidApp/src/main/java/com/msabhi/androidApp/counter/domain/sideeffects/CounterSideEffect.kt +++ b/androidApp/src/main/java/com/msabhi/androidApp/counter/domain/sideeffects/CounterSideEffect.kt @@ -24,16 +24,24 @@ import com.msabhi.flywheel.ActionState import com.msabhi.flywheel.StateReserve import com.msabhi.flywheel.attachments.DispatcherProvider import com.msabhi.flywheel.attachments.SideEffect +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch class CounterSideEffect(stateReserve: StateReserve, dispatchers: DispatcherProvider) : SideEffect(stateReserve, dispatchers) { init { + //Used start = CoroutineStart.UNDISPATCHED to ensure the actions are collected, especially, the very first actions + scope.launch(start = CoroutineStart.UNDISPATCHED) { actions.collect(::handle) } actionStates.onEach(::handle).launchIn(scope) } + private fun handle(action: Action) { + } + private fun handle(actionState: ActionState) { when (actionState.action) { diff --git a/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt b/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt index 85f6eb2..c0f8068 100644 --- a/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt +++ b/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt @@ -59,11 +59,6 @@ interface ErrorAction : Action { */ interface SkipReducer : Action -/** - * Internal Action to force distinct actions even when same actions are received in dispatch. - */ -private object ForceDistinctAction : Action - /** * Objects holding the state of a application/feature must implement [State]. */ @@ -338,18 +333,18 @@ class StateReserve( */ val states: Flow = setStates + /** + * Returns a [Flow] of actions that are passed through reducer. + */ + val actionStates: Flow> = transitionsMutable.filterIsInstance() + /** * Returns a [Flow] of actions that are passed through middleware and before reaching reducer. * This also captures actions modified by middlewares. * It is useful to react immediately to an received action. */ val actions: Flow = - mutableActions.distinctUntilChanged().filterNot { it is ForceDistinctAction } - - /** - * Returns a [Flow] of actions that are passed through reducer. - */ - val actionStates: Flow> = transitionsMutable.filterIsInstance() + merge(mutableActions, actionStates.map { it.action }).distinctUntilChanged() /** * Enable `enhancedStateMachine` config to listen for `transitions`. @@ -367,6 +362,7 @@ class StateReserve( } init { + config.scope.stateMachine( initialState = initialState, inputActions = inputActionsChannel, @@ -378,16 +374,12 @@ class StateReserve( ignoreDuplicateState = config.ignoreDuplicateState, enhancedStateMachine = config.enhancedStateMachine ) - - initialState.deferredState?.invokeOnCompletion { } } private fun dispatcher(action: Action) { if (config.debugMode && config.assertStateValues) { assertStateValues(action, state(), reduce, mutableStateChecker) } - mutableActions.tryEmit(action) - mutableActions.tryEmit(ForceDistinctAction) inputActionsChannel.trySend(action) } diff --git a/gradle.properties b/gradle.properties index 5d79c67..912632c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.native.enableDependencyPropagation=false org.gradle.daemon=true org.gradle.jvmargs=-Xmx2560m GROUP=com.msabhi -VERSION_NAME=1.1.3-RC +VERSION_NAME=1.1.4-SNAPSHOT POM_NAME=Flywheel POM_DESCRIPTION=Kotlin-Multiplatform state management library POM_URL=https://github.com/abhimuktheeswarar/Flywheel From ed02c550af928c9a279e9b19e347f8a0b50402f7 Mon Sep 17 00:00:00 2001 From: Abhi <15965088+abhimuktheeswarar@users.noreply.github.com> Date: Sun, 10 Oct 2021 12:03:27 +0530 Subject: [PATCH 2/3] Fix: Duplicate actions emission By using another channel to make sure the action passes through middlewares before it receives another Action. --- .../kotlin/com/msabhi/flywheel/Flywheel.kt | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt b/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt index c0f8068..31afdb1 100644 --- a/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt +++ b/flywheel/src/commonMain/kotlin/com/msabhi/flywheel/Flywheel.kt @@ -20,10 +20,7 @@ import com.msabhi.flywheel.utilities.MutableStateChecker import com.msabhi.flywheel.utilities.assertStateValues import com.msabhi.flywheel.utilities.name import kotlinx.coroutines.* -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.selects.select @@ -59,6 +56,11 @@ interface ErrorAction : Action { */ interface SkipReducer : Action +/** + * Internal Action to force distinct actions even when same actions are received in dispatch. + */ +private object ForceDistinctAction : Action + /** * Objects holding the state of a application/feature must implement [State]. */ @@ -299,6 +301,9 @@ class StateReserve( middlewares: List>?, ) { + private val actionsChannel: Channel = + Channel(capacity = Channel.UNLIMITED, onBufferOverflow = BufferOverflow.SUSPEND) + private val inputActionsChannel: Channel = Channel(capacity = Channel.UNLIMITED, onBufferOverflow = BufferOverflow.SUSPEND) @@ -344,7 +349,7 @@ class StateReserve( * It is useful to react immediately to an received action. */ val actions: Flow = - merge(mutableActions, actionStates.map { it.action }).distinctUntilChanged() + mutableActions.distinctUntilChanged().filterNot { it is ForceDistinctAction } /** * Enable `enhancedStateMachine` config to listen for `transitions`. @@ -363,6 +368,15 @@ class StateReserve( init { + config.scope.launch { + actionsChannel.consumeEach { action -> + if (isActive) { + mutableActions.tryEmit(action) + this@StateReserve.middlewares?.invoke(action) ?: dispatcher(action) + } + } + } + config.scope.stateMachine( initialState = initialState, inputActions = inputActionsChannel, @@ -380,6 +394,8 @@ class StateReserve( if (config.debugMode && config.assertStateValues) { assertStateValues(action, state(), reduce, mutableStateChecker) } + mutableActions.tryEmit(action) + mutableActions.tryEmit(ForceDistinctAction) inputActionsChannel.trySend(action) } @@ -387,8 +403,7 @@ class StateReserve( * It is the entry point for actions to update the StateReserve's state. */ fun dispatch(action: Action) { - mutableActions.tryEmit(action) - middlewares?.invoke(action) ?: dispatcher(action) + actionsChannel.trySend(action) } /** From f0d0f0c53e9687efe698974de1c649cc081d1ffe Mon Sep 17 00:00:00 2001 From: Abhi <15965088+abhimuktheeswarar@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:46:14 +0530 Subject: [PATCH 3/3] Fix: Duplicate actions emission By using another channel to make sure the action passes through middlewares before it receives another Action. Update Android Gradle Plugin. --- Flywheel.podspec | 2 +- README.md | 8 ++++---- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- gradle.properties | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Flywheel.podspec b/Flywheel.podspec index 83073be..66bae27 100644 --- a/Flywheel.podspec +++ b/Flywheel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Flywheel' - spec.version = '1.1.3-RC' + spec.version = '1.1.4-RC' spec.homepage = 'https://github.com/abhimuktheeswarar/Flywheel' spec.source = { :git => "https://github.com/abhimuktheeswarar/Flywheel.git", :tag => "v#{spec.version}" } spec.authors = 'Abhi Muktheeswarar' diff --git a/README.md b/README.md index bd14954..3b3ae16 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("com.msabhi:flywheel:1.1.3-RC") + implementation("com.msabhi:flywheel:1.1.4-RC") } } } @@ -41,7 +41,7 @@ kotlin { ```Kotlin dependencies { - implementation("com.msabhi:flywheel-android:1.1.3-RC") + implementation("com.msabhi:flywheel-android:1.1.4-RC") } ``` @@ -56,7 +56,7 @@ use_frameworks! target 'MyApp' do - pod 'Flywheel', '~> 1.1.3-RC' + pod 'Flywheel', '~> 1.1.4-RC' end ``` @@ -70,7 +70,7 @@ import PackageDescription let package = Package( name: "YOUR_PROJECT_NAME", dependencies: [ - .package(url: "https://github.com/abhimuktheeswarar/Flywheel.git", from: "1.1.3-RC"), + .package(url: "https://github.com/abhimuktheeswarar/Flywheel.git", from: "1.1.4-RC"), ] ) ``` diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 5fe0c53..0ba3db2 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -26,7 +26,7 @@ object Versions { const val minSdk = 21 const val targetSdk = 29 const val buildTools = "30.0.3" - const val androidGradlePlugin = "7.0.2" + const val androidGradlePlugin = "7.0.3" const val appCompat = "1.3.0" const val core = "1.6.0" diff --git a/gradle.properties b/gradle.properties index 912632c..e25d4eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.native.enableDependencyPropagation=false org.gradle.daemon=true org.gradle.jvmargs=-Xmx2560m GROUP=com.msabhi -VERSION_NAME=1.1.4-SNAPSHOT +VERSION_NAME=1.1.4-RC POM_NAME=Flywheel POM_DESCRIPTION=Kotlin-Multiplatform state management library POM_URL=https://github.com/abhimuktheeswarar/Flywheel