Skip to content

Commit 7e46d6c

Browse files
authored
Merge pull request #168 from badoo/issue-167-fix-serialization-behaviour
[#167] Reverted BaseFeature PostProcessor recursion behaviour change
2 parents de88bb1 + 9ae739f commit 7e46d6c

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed

documentation/whatsnew.md

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
### Pending changes
44

5+
No pending changes
6+
7+
### 1.3.1
8+
9+
#### Bug fixes
10+
11+
([#138](https://github.com/badoo/MVICore/issues/167)):
12+
Fixed regression related to BaseFeature actor.
13+
14+
The Actor subject was made serializable, and was also using a flatMap. Both of these changes caused a change in behaviour relating to the ordering of news (in features that have a PostProcessor which triggers extra actions).
15+
This change was made as part of introducing the optional `FeatureScheduler` to `BaseFeature`.
16+
17+
If you provide a `FeatureScheduler` and use a PostProcessor, please be aware that the ordering of your news could change.
18+
19+
The previous news ordering behaviour is actually a bug in BaseFeature caused by recursion, and will hopefully be addressed (as an opt in change) in a future release.
20+
21+
### 1.3.0
22+
523
#### Additions
624

725
([#147](https://github.com/badoo/MVICore/pull/147)):

mvicore/src/main/java/com/badoo/mvicore/feature/BaseFeature.kt

+19-8
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ open class BaseFeature<Wish : Any, in Action : Any, in Effect : Any, State : Any
4747
) : Feature<Wish, State, News> {
4848

4949
private val threadVerifier = if (featureScheduler == null) SameThreadVerifier(javaClass) else null
50-
private val actionSubject = PublishSubject.create<Action>().toSerialized()
50+
private val actionSubject = PublishSubject.create<Action>().toSerialized(featureScheduler)
5151
private val stateSubject = BehaviorSubject.createDefault(initialState)
5252
private val newsSubject = PublishSubject.create<News>()
5353
private val disposables = CompositeDisposable()
@@ -287,14 +287,25 @@ open class BaseFeature<Wish : Any, in Action : Any, in Effect : Any, State : Any
287287
private fun <T : Any> Observable<T>.observeOnFeatureScheduler(
288288
scheduler: FeatureScheduler?
289289
): Observable<T> =
290-
flatMap { value ->
291-
val upstream = Observable.just(value)
292-
if (scheduler == null || scheduler.isOnFeatureThread) {
293-
upstream
294-
} else {
295-
upstream
296-
.subscribeOn(scheduler.scheduler)
290+
if (scheduler != null) {
291+
flatMap { value ->
292+
val upstream = Observable.just(value)
293+
if (scheduler.isOnFeatureThread) {
294+
upstream
295+
} else {
296+
upstream
297+
.subscribeOn(scheduler.scheduler)
298+
}
297299
}
300+
} else {
301+
this
302+
}
303+
304+
private fun <T> Subject<T>.toSerialized(scheduler: FeatureScheduler?) =
305+
if (scheduler != null) {
306+
toSerialized()
307+
} else {
308+
this
298309
}
299310
}
300311
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.badoo.mvicore.feature
2+
3+
import com.badoo.mvicore.element.Actor
4+
import com.badoo.mvicore.element.NewsPublisher
5+
import com.badoo.mvicore.element.PostProcessor
6+
import com.badoo.mvicore.element.Reducer
7+
import com.badoo.mvicore.feature.PostProcessorTestFeature.*
8+
import io.reactivex.Observable
9+
import org.junit.Test
10+
11+
class BaseFeaturePostProcessorTest {
12+
@Test
13+
fun `GIVEN feature scheduler provided AND InitialTrigger sent WHEN post processor sends PostProcessorTrigger THEN news is in wish order`() {
14+
val feature = PostProcessorTestFeature(featureScheduler = FeatureSchedulers.TrampolineFeatureScheduler)
15+
val newsTestObserver = Observable.wrap(feature.news).test()
16+
feature.accept(Wish.InitialTrigger)
17+
18+
newsTestObserver.assertValues(News.TriggerNews, News.PostProcessorNews)
19+
}
20+
21+
/**
22+
* The post processor is recursively calling the actor, meaning the news is in reverse order in this scenario.
23+
*/
24+
@Test
25+
fun `GIVEN feature scheduler not provided AND InitialTrigger sent WHEN post processor sends PostProcessorTrigger THEN news is in recursive order`() {
26+
val feature = PostProcessorTestFeature(featureScheduler = null)
27+
val newsTestObserver = Observable.wrap(feature.news).test()
28+
feature.accept(Wish.InitialTrigger)
29+
30+
newsTestObserver.assertValues(News.PostProcessorNews, News.TriggerNews)
31+
}
32+
}
33+
34+
private class PostProcessorTestFeature(featureScheduler: FeatureScheduler?) :
35+
BaseFeature<Wish, Wish, Effect, State, News>(
36+
actor = ActorImpl(),
37+
initialState = State,
38+
reducer = ReducerImpl(),
39+
wishToAction = { it },
40+
newsPublisher = NewsPublisherImpl(),
41+
postProcessor = PostProcessorImpl(),
42+
featureScheduler = featureScheduler
43+
) {
44+
45+
sealed class Wish {
46+
object InitialTrigger : Wish()
47+
object PostProcessorTrigger : Wish()
48+
}
49+
50+
sealed class Effect {
51+
object TriggerEffect : Effect()
52+
object PostProcessorEffect : Effect()
53+
}
54+
55+
object State
56+
57+
sealed class News {
58+
object TriggerNews : News()
59+
object PostProcessorNews : News()
60+
}
61+
62+
class ActorImpl : Actor<State, Wish, Effect> {
63+
override fun invoke(state: State, wish: Wish): Observable<out Effect> =
64+
when (wish) {
65+
is Wish.InitialTrigger -> Observable.just(Effect.TriggerEffect)
66+
is Wish.PostProcessorTrigger -> Observable.just(Effect.PostProcessorEffect)
67+
}
68+
}
69+
70+
class ReducerImpl : Reducer<State, Effect> {
71+
override fun invoke(state: State, effect: Effect): State = state
72+
}
73+
74+
class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> {
75+
override fun invoke(action: Wish, effect: Effect, state: State): News =
76+
when (effect) {
77+
is Effect.TriggerEffect -> News.TriggerNews
78+
is Effect.PostProcessorEffect -> News.PostProcessorNews
79+
}
80+
}
81+
82+
class PostProcessorImpl : PostProcessor<Wish, Effect, State> {
83+
override fun invoke(action: Wish, effect: Effect, state: State): Wish? =
84+
if (action is Wish.InitialTrigger) {
85+
Wish.PostProcessorTrigger
86+
} else {
87+
null
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)