Skip to content

Commit 6afc885

Browse files
committed
Add beforeActionFlow to the Store, add nextDispatch(action:) to the RCKViewModel
- Renamed afterFlow to afterStateFlow - Renamed afterReset of Output to afterFlow
1 parent 929080a commit 6afc885

File tree

7 files changed

+70
-29
lines changed

7 files changed

+70
-29
lines changed

app/src/main/java/com/github/skyfe79/android/library/app/MainViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ class MainViewModel(application: Application): RCKViewModel<MainState>(applicati
4040
}
4141

4242
override fun on(newState: MainState) {
43-
route.accept(newState.route).afterReset(MainRoute.None)
43+
route.accept(newState.route).afterFlow(MainRoute.None)
4444
}
4545
}

app/src/main/java/com/github/skyfe79/android/library/app/examples/counter/CounterViewModel.kt

+10-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.app.Application
44
import com.github.skyfe79.android.library.app.examples.counter.action.AsyncIncreaseAction
55
import com.github.skyfe79.android.library.app.examples.counter.action.DecreaseAction
66
import com.github.skyfe79.android.library.app.examples.counter.action.IncreaseAction
7+
import com.github.skyfe79.android.library.app.examples.emojicollection.actions.AddEmojiAction
78
import com.github.skyfe79.android.reactcomponentkit.redux.*
89
import com.github.skyfe79.android.reactcomponentkit.viewmodel.RCKViewModel
910
import io.reactivex.Single
@@ -25,21 +26,23 @@ class CounterViewModel(application: Application): RCKViewModel<CounterState>(app
2526
fun asyncIncrease(state: CounterState, action: AsyncIncreaseAction) = asyncReducer(state, action) {
2627
Single.create<CounterState> { emitter ->
2728
Thread.sleep(1000L)
28-
withState { state ->
29-
emitter.onSuccess(state.copy(count = state.count + action.payload))
29+
withState {
30+
emitter.onSuccess(copy(count = count + action.payload))
3031
}
3132
}.toObservable()
3233
}
3334

35+
fun increasement(payload: Int) = setState {
36+
copy(count = count + payload)
37+
}
38+
3439
override fun setupStore() {
3540
initStore { store ->
3641
store.initialState(CounterState(0))
3742

3843
store.flow<AsyncIncreaseAction>(
3944
{ _, _ ->
40-
setState {
41-
it.copy(asyncCount = Async.Loading)
42-
}
45+
setState { copy(asyncCount = Async.Loading) }
4346
},
4447
{ state, action ->
4548
state.copy(count = state.count + action.payload)
@@ -48,8 +51,8 @@ class CounterViewModel(application: Application): RCKViewModel<CounterState>(app
4851
asyncFlow { action ->
4952
Single.create<CounterState> { emitter ->
5053
Thread.sleep(2000L)
51-
withState { state ->
52-
emitter.onSuccess(state.copy(count = state.count + action.payload, asyncCount = Async.Success(state.count + action.payload)))
54+
withState {
55+
emitter.onSuccess(copy(count = count + action.payload, asyncCount = Async.Success(count + action.payload)))
5356
}
5457
}.toObservable()
5558
}

app/src/main/java/com/github/skyfe79/android/library/app/examples/emojicollection/EmojiCollectionViewModel.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ class EmojiCollectionViewModel(application: Application): RCKViewModel<EmojiColl
3535
initStore { store ->
3636

3737
store.initialState(EmojiCollectionState())
38-
store.afterFlow({
39-
it.copy(route = EmojiRoute.None)
40-
})
41-
4238

4339
store.flow<ClickEmojiAction>({ state, action ->
4440
state.copy(route = EmojiRoute.AlertEmoji(action.emoji))
@@ -58,12 +54,16 @@ class EmojiCollectionViewModel(application: Application): RCKViewModel<EmojiColl
5854
::shuffleEmoji,
5955
{ state, _ -> makeItemModels(state) }
6056
)
57+
58+
store.afterStateFlow({
59+
copy(route = EmojiRoute.None)
60+
})
6161
}
6262
}
6363

6464
override fun on(newState: EmojiCollectionState) {
6565
itemModels.accept(newState.itemModels)
66-
routes.accept(newState.route).afterReset(EmojiRoute.None)
66+
routes.accept(newState.route).afterFlow(EmojiRoute.None)
6767
}
6868

6969
override fun on(error: Error) {

reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Effect.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package com.github.skyfe79.android.reactcomponentkit.redux
22

33
// Utility for something like as reset some state.
44
// Run it after dispatching new state to Components
5-
typealias Effect<STATE> = (STATE) -> STATE
5+
typealias Effect<STATE> = STATE.(STATE) -> STATE

reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Output.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Output<T>(defaultValue: T?) {
1010
val value: T?
1111
get() = behaviorRelay.value
1212

13-
fun accept(value: T, withoutCompare: Boolean = false): ResetChanin {
13+
fun accept(value: T, withoutCompare: Boolean = false): FlowChanin {
1414
if (withoutCompare) {
1515
behaviorRelay.accept(value)
1616
} else {
@@ -19,15 +19,15 @@ class Output<T>(defaultValue: T?) {
1919
behaviorRelay.accept(value)
2020
}
2121
}
22-
return ResetChanin()
22+
return FlowChanin()
2323
}
2424

2525
fun asObservable(): Observable<T> {
2626
return behaviorRelay.observeOn(AndroidSchedulers.mainThread())
2727
}
2828

29-
inner class ResetChanin {
30-
fun afterReset(value: T) {
29+
inner class FlowChanin {
30+
fun afterFlow(value: T) {
3131
accept(value, withoutCompare = true)
3232
}
3333
}

reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/redux/Store.kt

+14-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import io.reactivex.schedulers.Schedulers
88
import kotlin.reflect.KClass
99

1010

11+
12+
1113
class Store<S: State> {
1214

1315
inner class Flow<STATE: State, A: Action>(private val reducerList: List<Any>) {
@@ -36,6 +38,7 @@ class Store<S: State> {
3638

3739
lateinit var state: S
3840
internal set
41+
private var beforeActionFlow: (S.(Action) -> Action)? = null
3942
lateinit var actionFlowMap: MutableMap<KClass<*>, Flow<S, Action>>
4043
private set
4144
private lateinit var effects: List<Effect<S>>
@@ -65,6 +68,15 @@ class Store<S: State> {
6568
disposables.clear()
6669
}
6770

71+
fun beforeActionFlow(actionFlow: S.(Action) -> Action) {
72+
this.beforeActionFlow = actionFlow
73+
}
74+
75+
@Suppress("UNCHECKED_CAST")
76+
internal fun actionFlow(action: Action): Action {
77+
return beforeActionFlow?.invoke(this.state.copyState() as S, action) ?: action
78+
}
79+
6880
/**
6981
* Make a flow of reducers for an action
7082
*/
@@ -75,7 +87,7 @@ class Store<S: State> {
7587
/**
7688
* do some side effect after finishing a flow.
7789
*/
78-
fun afterFlow(vararg effects: Effect<S>) {
90+
fun afterStateFlow(vararg effects: Effect<S>) {
7991
this.effects = effects.toList()
8092
}
8193

@@ -86,7 +98,7 @@ class Store<S: State> {
8698
internal fun doAfterEffects() {
8799
var mutatedState = state
88100
effects.forEach {
89-
mutatedState = it(mutatedState)
101+
mutatedState = it(mutatedState, mutatedState)
90102
}
91103
state = mutatedState
92104
}

reactcomponentkit/src/main/java/com/github/skyfe79/android/reactcomponentkit/viewmodel/RCKViewModel.kt

+35-9
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,25 @@ import java.lang.ref.WeakReference
1717
import java.util.concurrent.locks.ReentrantLock
1818

1919
abstract class RCKViewModel<S: State>(application: Application): AndroidViewModel(application) {
20-
val token: Token =
21-
Token()
20+
val token: Token = Token()
2221

2322
private val rx_action: BehaviorRelay<Action> = BehaviorRelay.createDefault(
2423
VoidAction
2524
)
2625
private val store = Store<S>()
2726
private val disposables = CompositeDisposable()
27+
private var actionQueue: Queue<Action> = Queue()
2828
private val writeLock = ReentrantLock()
2929
private val readLock = ReentrantLock()
3030
private var subscribers: MutableList<WeakReference<StateSubscriber>> = mutableListOf()
3131

3232
init {
33-
setupRxStream()
33+
this.setupRxStream()
3434
this.setupStore()
3535
}
3636

3737
override fun onCleared() {
38-
dispose()
38+
this.dispose()
3939
super.onCleared()
4040
}
4141

@@ -44,6 +44,7 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
4444
*/
4545
fun dispose() {
4646
RCK.unregisterViewModel(token)
47+
actionQueue.clear()
4748
subscribers = mutableListOf()
4849
disposables.dispose()
4950
store.deinitialize()
@@ -55,12 +56,21 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
5556
.filter { action ->
5657
action !is VoidAction
5758
}
59+
.filter { action ->
60+
store.actionFlow(action) !is VoidAction
61+
}
5862
.flatMap { action ->
5963
store.dispatch(action).toObservable()
6064
}
6165
.observeOn(AndroidSchedulers.mainThread())
6266
.doAfterNext {
6367
store.doAfterEffects()
68+
if (actionQueue.isNotEmpty) {
69+
val nextAction = actionQueue.dequeue()
70+
nextAction?.let {
71+
rx_action.accept(it)
72+
}
73+
}
6474
}
6575
.subscribe { newState ->
6676
if (newState.error != null) {
@@ -70,6 +80,8 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
7080
} else {
7181
dispatchStateToSubscribers(newState)
7282
}
83+
84+
7385
}
7486

7587
disposables.add(disposable)
@@ -101,6 +113,17 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
101113
rx_action.accept(action)
102114
}
103115

116+
/**
117+
* dispatch action on the next run loop to the store.
118+
*/
119+
fun nextDispatch(action: Action) {
120+
if (actionQueue.isEmpty) {
121+
rx_action.accept(action)
122+
} else {
123+
actionQueue.enqueue(action)
124+
}
125+
}
126+
104127
/**
105128
* Called when receive the new state from store
106129
*/
@@ -129,12 +152,14 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
129152
* Set state and dispatch the mutated state to subscribers
130153
*/
131154
@Suppress("UNCHECKED_CAST")
132-
fun setState(block: RCKViewModel<S>.(S) -> S): S {
155+
fun setState(block: S.(S) -> S): S {
133156
writeLock.lock()
134157
try {
135-
val newState = block(this.store.state.copyState() as S)
158+
val state = this.store.state.copyState() as S
159+
val newState = state.block(state)
160+
this.store.state = newState
136161
runOnUiThread {
137-
this.on(newState)
162+
dispatchStateToSubscribers(newState)
138163
}
139164
return newState
140165
} finally {
@@ -146,10 +171,11 @@ abstract class RCKViewModel<S: State>(application: Application): AndroidViewMode
146171
* Read state value
147172
*/
148173
@Suppress("UNCHECKED_CAST")
149-
fun <R> withState(block: RCKViewModel<S>.(S) -> R): R {
174+
fun <R> withState(block: S.(S) -> R): R {
150175
readLock.lock()
151176
try {
152-
return block(this.store.state.copyState() as S)
177+
val state = this.store.state.copyState() as S
178+
return state.block(state)
153179
} finally {
154180
readLock.unlock()
155181
}

0 commit comments

Comments
 (0)