From 6afc885085884ede3bc9378cd853b5f75295f43f Mon Sep 17 00:00:00 2001 From: skyfe79 Date: Tue, 3 Sep 2019 23:02:04 +0900 Subject: [PATCH 1/2] Add beforeActionFlow to the Store, add nextDispatch(action:) to the RCKViewModel - Renamed afterFlow to afterStateFlow - Renamed afterReset of Output to afterFlow --- .../android/library/app/MainViewModel.kt | 2 +- .../app/examples/counter/CounterViewModel.kt | 17 ++++--- .../EmojiCollectionViewModel.kt | 10 ++--- .../android/reactcomponentkit/redux/Effect.kt | 2 +- .../android/reactcomponentkit/redux/Output.kt | 8 ++-- .../android/reactcomponentkit/redux/Store.kt | 16 ++++++- .../viewmodel/RCKViewModel.kt | 44 +++++++++++++++---- 7 files changed, 70 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt b/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt index fdc8a95..9f8456d 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt @@ -40,6 +40,6 @@ class MainViewModel(application: Application): RCKViewModel(applicati } override fun on(newState: MainState) { - route.accept(newState.route).afterReset(MainRoute.None) + route.accept(newState.route).afterFlow(MainRoute.None) } } \ No newline at end of file diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/counter/CounterViewModel.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/counter/CounterViewModel.kt index 5fa27c5..b02c70a 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/counter/CounterViewModel.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/counter/CounterViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import com.github.skyfe79.android.library.app.examples.counter.action.AsyncIncreaseAction import com.github.skyfe79.android.library.app.examples.counter.action.DecreaseAction import com.github.skyfe79.android.library.app.examples.counter.action.IncreaseAction +import com.github.skyfe79.android.library.app.examples.emojicollection.actions.AddEmojiAction import com.github.skyfe79.android.reactcomponentkit.redux.* import com.github.skyfe79.android.reactcomponentkit.viewmodel.RCKViewModel import io.reactivex.Single @@ -25,21 +26,23 @@ class CounterViewModel(application: Application): RCKViewModel(app fun asyncIncrease(state: CounterState, action: AsyncIncreaseAction) = asyncReducer(state, action) { Single.create { emitter -> Thread.sleep(1000L) - withState { state -> - emitter.onSuccess(state.copy(count = state.count + action.payload)) + withState { + emitter.onSuccess(copy(count = count + action.payload)) } }.toObservable() } + fun increasement(payload: Int) = setState { + copy(count = count + payload) + } + override fun setupStore() { initStore { store -> store.initialState(CounterState(0)) store.flow( { _, _ -> - setState { - it.copy(asyncCount = Async.Loading) - } + setState { copy(asyncCount = Async.Loading) } }, { state, action -> state.copy(count = state.count + action.payload) @@ -48,8 +51,8 @@ class CounterViewModel(application: Application): RCKViewModel(app asyncFlow { action -> Single.create { emitter -> Thread.sleep(2000L) - withState { state -> - emitter.onSuccess(state.copy(count = state.count + action.payload, asyncCount = Async.Success(state.count + action.payload))) + withState { + emitter.onSuccess(copy(count = count + action.payload, asyncCount = Async.Success(count + action.payload))) } }.toObservable() } diff --git a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt index 424f2b3..f0d88a6 100644 --- a/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt +++ b/app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt @@ -35,10 +35,6 @@ class EmojiCollectionViewModel(application: Application): RCKViewModel store.initialState(EmojiCollectionState()) - store.afterFlow({ - it.copy(route = EmojiRoute.None) - }) - store.flow({ state, action -> state.copy(route = EmojiRoute.AlertEmoji(action.emoji)) @@ -58,12 +54,16 @@ class EmojiCollectionViewModel(application: Application): RCKViewModel makeItemModels(state) } ) + + store.afterStateFlow({ + copy(route = EmojiRoute.None) + }) } } override fun on(newState: EmojiCollectionState) { itemModels.accept(newState.itemModels) - routes.accept(newState.route).afterReset(EmojiRoute.None) + routes.accept(newState.route).afterFlow(EmojiRoute.None) } override fun on(error: Error) { diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Effect.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Effect.kt index f12dd33..bcf0940 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Effect.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Effect.kt @@ -2,4 +2,4 @@ package com.github.skyfe79.android.reactcomponentkit.redux // Utility for something like as reset some state. // Run it after dispatching new state to Components -typealias Effect = (STATE) -> STATE \ No newline at end of file +typealias Effect = STATE.(STATE) -> STATE \ No newline at end of file diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt index 06b2374..2def736 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt @@ -10,7 +10,7 @@ class Output(defaultValue: T?) { val value: T? get() = behaviorRelay.value - fun accept(value: T, withoutCompare: Boolean = false): ResetChanin { + fun accept(value: T, withoutCompare: Boolean = false): FlowChanin { if (withoutCompare) { behaviorRelay.accept(value) } else { @@ -19,15 +19,15 @@ class Output(defaultValue: T?) { behaviorRelay.accept(value) } } - return ResetChanin() + return FlowChanin() } fun asObservable(): Observable { return behaviorRelay.observeOn(AndroidSchedulers.mainThread()) } - inner class ResetChanin { - fun afterReset(value: T) { + inner class FlowChanin { + fun afterFlow(value: T) { accept(value, withoutCompare = true) } } diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt index 34fb8ec..9f78988 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt @@ -8,6 +8,8 @@ import io.reactivex.schedulers.Schedulers import kotlin.reflect.KClass + + class Store { inner class Flow(private val reducerList: List) { @@ -36,6 +38,7 @@ class Store { lateinit var state: S internal set + private var beforeActionFlow: (S.(Action) -> Action)? = null lateinit var actionFlowMap: MutableMap, Flow> private set private lateinit var effects: List> @@ -65,6 +68,15 @@ class Store { disposables.clear() } + fun beforeActionFlow(actionFlow: S.(Action) -> Action) { + this.beforeActionFlow = actionFlow + } + + @Suppress("UNCHECKED_CAST") + internal fun actionFlow(action: Action): Action { + return beforeActionFlow?.invoke(this.state.copyState() as S, action) ?: action + } + /** * Make a flow of reducers for an action */ @@ -75,7 +87,7 @@ class Store { /** * do some side effect after finishing a flow. */ - fun afterFlow(vararg effects: Effect) { + fun afterStateFlow(vararg effects: Effect) { this.effects = effects.toList() } @@ -86,7 +98,7 @@ class Store { internal fun doAfterEffects() { var mutatedState = state effects.forEach { - mutatedState = it(mutatedState) + mutatedState = it(mutatedState, mutatedState) } state = mutatedState } diff --git a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/viewmodel/RCKViewModel.kt b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/viewmodel/RCKViewModel.kt index 5bad4d1..1da1196 100644 --- a/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/viewmodel/RCKViewModel.kt +++ b/reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/viewmodel/RCKViewModel.kt @@ -17,25 +17,25 @@ import java.lang.ref.WeakReference import java.util.concurrent.locks.ReentrantLock abstract class RCKViewModel(application: Application): AndroidViewModel(application) { - val token: Token = - Token() + val token: Token = Token() private val rx_action: BehaviorRelay = BehaviorRelay.createDefault( VoidAction ) private val store = Store() private val disposables = CompositeDisposable() + private var actionQueue: Queue = Queue() private val writeLock = ReentrantLock() private val readLock = ReentrantLock() private var subscribers: MutableList> = mutableListOf() init { - setupRxStream() + this.setupRxStream() this.setupStore() } override fun onCleared() { - dispose() + this.dispose() super.onCleared() } @@ -44,6 +44,7 @@ abstract class RCKViewModel(application: Application): AndroidViewMode */ fun dispose() { RCK.unregisterViewModel(token) + actionQueue.clear() subscribers = mutableListOf() disposables.dispose() store.deinitialize() @@ -55,12 +56,21 @@ abstract class RCKViewModel(application: Application): AndroidViewMode .filter { action -> action !is VoidAction } + .filter { action -> + store.actionFlow(action) !is VoidAction + } .flatMap { action -> store.dispatch(action).toObservable() } .observeOn(AndroidSchedulers.mainThread()) .doAfterNext { store.doAfterEffects() + if (actionQueue.isNotEmpty) { + val nextAction = actionQueue.dequeue() + nextAction?.let { + rx_action.accept(it) + } + } } .subscribe { newState -> if (newState.error != null) { @@ -70,6 +80,8 @@ abstract class RCKViewModel(application: Application): AndroidViewMode } else { dispatchStateToSubscribers(newState) } + + } disposables.add(disposable) @@ -101,6 +113,17 @@ abstract class RCKViewModel(application: Application): AndroidViewMode rx_action.accept(action) } + /** + * dispatch action on the next run loop to the store. + */ + fun nextDispatch(action: Action) { + if (actionQueue.isEmpty) { + rx_action.accept(action) + } else { + actionQueue.enqueue(action) + } + } + /** * Called when receive the new state from store */ @@ -129,12 +152,14 @@ abstract class RCKViewModel(application: Application): AndroidViewMode * Set state and dispatch the mutated state to subscribers */ @Suppress("UNCHECKED_CAST") - fun setState(block: RCKViewModel.(S) -> S): S { + fun setState(block: S.(S) -> S): S { writeLock.lock() try { - val newState = block(this.store.state.copyState() as S) + val state = this.store.state.copyState() as S + val newState = state.block(state) + this.store.state = newState runOnUiThread { - this.on(newState) + dispatchStateToSubscribers(newState) } return newState } finally { @@ -146,10 +171,11 @@ abstract class RCKViewModel(application: Application): AndroidViewMode * Read state value */ @Suppress("UNCHECKED_CAST") - fun withState(block: RCKViewModel.(S) -> R): R { + fun withState(block: S.(S) -> R): R { readLock.lock() try { - return block(this.store.state.copyState() as S) + val state = this.store.state.copyState() as S + return state.block(state) } finally { readLock.unlock() } From f4b7f5221997a49fcd68b2353f7c1cd982a68b99 Mon Sep 17 00:00:00 2001 From: skyfe79 Date: Tue, 3 Sep 2019 23:03:50 +0900 Subject: [PATCH 2/2] Update version info --- reactcomponentkit/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reactcomponentkit/build.gradle b/reactcomponentkit/build.gradle index 4331e22..6d0f132 100644 --- a/reactcomponentkit/build.gradle +++ b/reactcomponentkit/build.gradle @@ -17,7 +17,7 @@ publish { userOrg = 'skyfe79' groupId = 'com.github.skyfe79.android' artifactId = 'reactcomponentkit' - publishVersion = '2.0.0' + publishVersion = '2.0.1' desc = 'AndroidReactComponentKit = Component + MVVM + Redux for Android!!!' website = 'https://github.com/ReactComponentKit/AndroidReactComponentKit' } @@ -29,8 +29,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 29 - versionCode 20 - versionName "2.0.0" + versionCode 21 + versionName "2.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }