Share
Embed
Architectural Journey
At Badoo, we have a long history when it comes to Android development. Since the beginning, the product has experienced huge growth, and our codebase and our team have grown with it. We now have over 420 million registrations, as well as several new apps to support. Over the years we’ve tried MVP, Clean architecture, MVI, and many more approaches, some of them brewed in-house. In this talk we’ll go on an architectural journey and discuss some of the major problems we needed to find solutions for in the last few years, before ending on what is ahead of us.
Zsolt Kocsi
April 23, 2019
Transcript
181 slides
-
View Intent Model Model changes Immutable state Reducer Pros Single
point of mutation Easier to understand 13 -
1. MVP 2. Clean architecture 3. ??? What we need
Immutable state How to do it best ??? 14 -
1. MVP 2. Clean architecture 3. Proto-MVI Inspired by Redux
Frankenstein’s monster of • MVP • Clean architecture • Immutable state stores • RxJava 15 -
1. MVP 2. Clean architecture 3. Proto-MVI Pros Easier to
reason about what can happen to the state State problems gone! Cons We needed structure 16 -
1. MVP 2. Clean architecture 3. Proto-MVI 4. Experiments 5.
MVICore https://github.com/badoo/MVICore 18 -
22 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• R Fe -
23 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { }¢ R Fe -
24 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke( }¢ R Fe -
25 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State }¢ R Fe -
26 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish) }¢ R Fe -
27 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = }¢ R Fe -
28 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { }™ }¢ R Fe -
29 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { IncreaseCounter -> state.copy( counter = state.counter + 1 )§ }™ }¢ R Fe -
30 data class State( val counter: Int = 0 )£
sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { IncreaseCounter -> state.copy( counter = state.counter + 1 )§ is MultiplyBy -> state.copy( counter = state.counter * wish.value )¡ }™ }¢ R Fe -
31 R Fe class SimpleFeature : ReducerFeature<Wish, State, Nothing>( initialState
= State(), reducer = ReducerImpl() ) { data class State( val counter: Int = 0 )£ sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { IncreaseCounter -> state.copy( counter = state.counter + 1 )§ is MultiplyBy -> state.copy( counter = state.counter * wish.value )¡ }™ }¢ } -
36 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )• sealed£class£Wish£{ ¢¢¢¢object StartedLoading : Wish() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Wish() §§§§data class Failed(valªthrowable:ªThrowable)ª: Wish() }º R Fe -
37 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )• sealed£class£Wish£{ ¢¢¢¢object StartedLoading : Wish() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Wish() §§§§data class Failed(valªthrowable:ªThrowable)ª: Wish() }º class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { StartedLoading -> state.copy( isLoading = true )º R Fe -
38 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )• sealed£class£Wish£{ ¢¢¢¢object StartedLoading : Wish() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Wish() §§§§data class Failed(valªthrowable:ªThrowable)ª: Wish() }º class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { StartedLoading -> state.copy( isLoading = true )º is Success -> state.copy( isLoading = false, hasError = false, payload = wish.payload )¡ R Fe -
39 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )• sealed£class£Wish£{ ¢¢¢¢object StartedLoading : Wish() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Wish() §§§§data class Failed(valªthrowable:ªThrowable)ª: Wish() }º class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { StartedLoading -> state.copy( isLoading = true )º is Success -> state.copy( isLoading = false, hasError = false, payload = wish.payload )¡ is Failed -> state.copy( isLoading = false, hasError = true )™ R Fe -
41 State machines Reducer only Side-effects as loop Reducer Feature
Command Wish State -
42 class Reducer( private val initialState: State = State(), private
val ex: Executor ) : StateReducer<State, Message, Effect> { override fun invoke(state: State, msg: Message): Pair<State, Command<Effect>?> = when (msg) { is Reset -> initialState to null is Get -> state to { ex.getSettings(state) }.takeIf { !state.loaded } is Retry -> state.retry() to { ex.getSettings(state)“}.takeIf { state.status == FAILURE } is Updated -> state.change(msg.setting) to { executor.saveSettings()≥}.takeIf { msg.saveImmediately }˜ is Done -> state to { ex.saveSettings(state.getValues(), state.mode) }∫ is Logout -> state to { ex.logout() }å is DeleteAccount -> state to { ex.deleteAccount() }~ is PublicProfile -> state.profile() to { ex.saveAndGet(state.profile()) ±}.takeIf { msg.mode == state.mode }^ is Get -> state.onGet(msg) to null is Save -> state.onSave(msg) to null is Logout -> state to null }@ }£ R Fe -
43 State machines Reducer only Side-effects as loop Reducer Feature
Command Wish State -
44 Reducer Feature Actor State machines Reducer only Side-effects with
Actor Wish State Effect -
45 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )å≈ R Fe Ac -
46 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )å≈ sealed•class•Wish•{ ••••object•LoadSomething•:•Wish() }Ω R Fe Ac -
47 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )å≈ sealed•class•Wish•{ ••••object•LoadSomething•:•Wish() }Ω sealed£class£Effect£{ ¢¢¢¢object StartedLoading : Effect() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Effect() §§§§data class Failed(valªthrowable:ªThrowable)ª:ªEffect() }º R Fe Ac -
48 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )å≈ sealed£class£Effect£{ ¢¢¢¢object StartedLoading : Effect() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Effect() §§§§data class Failed(valªthrowable:ªThrowable)ª:ªEffect() }º R Fe Ac -
49 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean
= false, val payload: Any? = null )å sealed£class£Effect£{ ¢¢¢¢object StartedLoading : Effect() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Effect() §§§§data class Failed(valªthrowable:ªThrowable)ª:ªEffect() }™ class ReducerImpl : Reducer<State, Effect> { override fun invoke(state: State, effect: Effect): State = when (effect) { StartedLoading -> state.copy( isLoading = true ) is Success -> state.copy( isLoading = false, hasError = false, payload = wish.payload ) is Failed -> state.copy( isLoading = false, hasError = true ) R Fe Ac -
50 sealed£class£Effect™{ ¢¢¢¢object StartedLoading : Effect() ∞∞∞∞data class Success(val¶payload:¶Any)¶:¶Effect() §§§§data
class Failed(valªthrowable:ªThrowable)ª:ªEffect() }™ R Fe Ac -
51 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect™{ object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ R Fe Ac -
52 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { }º R Fe Ac -
53 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() }º R Fe Ac -
54 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): }º R Fe Ac -
55 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = }º R Fe Ac -
56 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = when (wish) { is LoadSomething -> }£ }º R Fe Ac -
57 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = when (wish) { is LoadSomething -> service.invoke() .toObservable() }£ }º R Fe Ac -
58 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = when (wish) { is LoadSomething -> service.invoke() .toObservable() .map { Success(it) as Effect } }£ }º R Fe Ac -
59 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = when (wish) { is LoadSomething -> service.invoke() .toObservable() .map { Success(it) as Effect } .startWith(Observable.just(StartedLoading)) }£ }º R Fe Ac -
60 sealed class Wish { object LoadSomething : Wish() }¡
sealed class Effect { object StartedLoading : Effect() data class Success(val payload: Any) : Effect() data class Failed(val throwable: Throwable) : Effect() }™ class ActorImpl : Actor<State, Wish, Effect> { private val service: () -> Single<Any> = TODO() override fun invoke(state: State, wish: Wish): Observable<out Effect> = when (wish) { is LoadSomething -> service.invoke() .toObservable() .map { Success(it) as Effect } .startWith(Observable.just(StartedLoading)) .onErrorReturn { Failed(it) } }£ }º R Fe Ac -
61 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, Nothing>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() ) R Fe Ac -
62 Reducer Feature Actor State machines Reducer only Side-effects with
Actor Wish State Effect -
64 R Fe Ac data class State( val isLoading: Boolean
= false, valªhasError:ªBoolean = false, val payload: Any? = null )å class ReducerImpl : Reducer<State, Effect> { override fun invoke(state: State, effect: Effect): State = when (effect) { is Failed -> state.copy( isLoading = false, hasError = true ) valªhasError:ªBoolean )å is Failed -> state.copy( hasError = true ) -
65 R Fe Ac data class State( val isLoading: Boolean
= false, valªhasError:ªBoolean = false, val payload: Any? = null )å class ReducerImpl : Reducer<State, Effect> { override fun invoke(state: State, effect: Effect): State = when (effect) { is Failed -> state.copy( isLoading = false, hasError = true ) valªhasError:ªBoolean )å is Failed -> state.copy( hasError = true ) -
71 Reducer Feature Actor Wish State Effect State machines Reducer
only Side-effects with Actor Events -
72 News Publ. Reducer Feature Actor Wish State Effect News
State machines Reducer only Side-effects with Actor Events -
73 News Publ. Reducer Feature Actor Wish State Effect News
State machines Reducer only Side-effects with Actor Events -
74 Reducer Feature Actor News Publ. Wish State Effect News
State machines Reducer only Side-effects with Actor Events -
75 Feature News Publ. Reducer Actor Wish State Effect News
State machines Reducer only Side-effects with Actor Events -
76 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, Nothing>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N -
77 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, >( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N -
78 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N -
79 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ }º F N R A W S E N -
80 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, }¡ }º F N R A W S E N -
81 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, effect: Effect, }¡ }º F N R A W S E N -
82 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, effect: Effect, state: State): }¡ }º F N R A W S E N -
83 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, effect: Effect, state: State): News? = }¡ }º F N R A W S E N -
84 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, effect: Effect, state: State): News? = when (effect) { is Failed -> News.NetworkRequestFailed( effect.throwable ) else -> null }™ }¡ }º F N R A W S E N -
85 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =
State(), actor = ActorImpl(), reducer = ReducerImpl(), newsPublisher = NewsPublisherImpl() )π{ sealed class News { data class NetworkRequestFailed(val throwable: Throwable) : News() }£ class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> { override fun invoke(wish: Wish, effect: Effect, state: State): News? = when (effect) { is Failed -> News.NetworkRequestFailed( effect.throwable ) else -> null }™ }¡ }º F N R A W S E N -
86 feature.news .subscribe { when (it) { is News.NetworkRequestFailed ->
{ showToast() } }} F N R A W S E N -
91 F V Y X Pros Easy Cons No single-source
of truth Connecting components Data-flow -
95 Pros Single source of truth! Better control over flow
F V Y X Connecting components Data-flow -
96 Pros Single source of truth! Better control over flow
F V Connecting components Data-flow -
97 fun connect() { val source = Observable.just(1, 2, 3)
val consumer = Consumer<Int> { Log.d("New number!", it.toString()) }¢ val disposable = source.subscribe(consumer) }ƒ -
98 package io.reactivex; /** * Represents a basic, non-backpressured {@link
Observab * consumable via an {@link Observer}. * * @param <T> the element type * @since 2.0 */ public interface ObservableSource<T> { /** * Subscribes the given Observer to this ObservableS * @param observer the Observer, not null * @throws NullPointerException if {@code observer} */ void subscribe(@NonNull Observer<? super T> observer); } -
99 package io.reactivex.functions; /** * A functional interface (callback) that
accepts a singl * @param <T> the value type */ public interface Consumer<T> { /** * Consume the given value. * @param t the value * @throws Exception on error */ void accept(T t) throws Exception; } -
102 interface Feature : Consumer<Wish>, ObservableSource<State> { sealed class Wish
data class State }£ interface View : Consumer<State>, ObservableSource<Wish> { }¡ -
105 interface Feature : Consumer<Wish>, ObservableSource<State> { sealed class Wish
data class State } interface View : Consumer<State>, ObservableSource<Wish> { } -
106 interface Feature : Consumer<Wish>, ObservableSource<State> { sealed class Wish
data class State } interface View : Consumer<ViewModel>, ObservableSource<View.Event> { data class ViewModel sealed class Event } -
107 F V Pros No coupling :) Cons ? Connecting
components Data-flow Coupling -
108 /** * Gluing layer between UI and Feature */
interface Boundary<in UiEvent : Any, ViewModel : Any> : Disposable { fun onUiEvent(uiEvent: UiEvent) val viewModels: Observable<ViewModel> } Connecting components Data-flow Coupling -
109 Pros No coupling :) Cons Not flexible :( F
V F ??? Connecting components Data-flow Coupling -
110 F F ??? Pros No coupling :) Cons Not
flexible :( Connecting components Data-flow Coupling -
111 F V X Y ??? Pros No coupling :)
Cons Not flexible :( Connecting components Data-flow Coupling -
115 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
116 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
117 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
118 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
119 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )£
val input: Consumer<String> = Consumer { System.out.println(it) }£ val disposable = Observable .wrap(output) .subscribe(input) -
120 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )£
val input: Consumer<String> = Consumer { System.out.println(it) }£ val binder = Binder() binder.bind(output to input) -
121 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )£
val input: Consumer<> = Consumer { System.out.println(it) }£ val binder = Binder() binder.bind(output to input) -
122 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )£
val input: Consumer<Int> = Consumer { System.out.println(it) }£ val binder = Binder() binder.bind(output to input) -
123 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )££
val input: Consumer<Int> = Consumer { System.out.println(it) }£ val transformer : (String) -> Int = { it.length }£ val binder = Binder() binder.bind(output to input) -
124 val output: ObservableSource<String> = Observable.just( "item1", "item2", "item3" )£
val input: Consumer<Int> = Consumer { System.out.println(it) }£ val transformer : (String) -> Int = { it.length }£ val binder = Binder() binder.bind(output to input using transformer) -
125 binder.bind(view to feature using EventToWish)£ binder.bind(feature to view using
StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
126 binder.bind(view to analyticsTracker) binder.bind(view to feature using EventToWish) binder.bind(feature
to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) binder.bind(view to analyticsTracker) -
127 binder.bind(view to analyticsTracker) binder.bind(view to feature using EventToWish) binder.bind(feature
to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
128 val binder = Binder() binder.bind(view to analyticsTracker) binder.bind(view to
feature using EventToWish) binder.bind(feature to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
129 val lifecycle: Lifecycle = TODO() val binder = Binder()£
binder.bind(view to analyticsTracker) binder.bind(view to feature using EventToWish) binder.bind(feature to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
130 val lifecycle: Lifecycle = TODO() val binder = Binder(lifecycle)£
binder.bind(view to analyticsTracker) binder.bind(view to feature using EventToWish) binder.bind(feature to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
131 C S Begin End Begin End Connecting components Data-flow
Coupling Scoping -
132 C S Lifecycle sources Android / Create-Destroy Android /
Start-Stop Android / Resume-Pause From Observable Manual Connecting components Data-flow Coupling Scoping -
133 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
135 F V V X Y F V Connecting components
Data-flow Coupling Scoping -
138 binder.bind(view to analyticsTracker) binder.bind(view to feature using EventToWish) binder.bind(feature
to view using StateToViewModel) binder.bind(feature to greenX using StateToGX) binder.bind(feature to greenY using StateToGY) -
143 package io.reactivex.functions; /** * A functional interface (callback) that
accepts a singl * @param <T> the value type */ public interface Consumer<T> { /** * Consume the given value. * @param t the value * @throws Exception on error */ void accept(T t) throws Exception; } -
147 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :
Consumer<T>¡{ override fun accept(t: T) { }& }º -
148 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :
Consumer<T>¡{ override fun accept(t: T) { wrapped.accept(t) }& }º -
149 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :
Consumer<T>¡{ override fun accept(t: T) { // do whatever here wrapped.accept(t) // do whatever here }& }º -
150 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :
Consumer<T>¡{ override fun accept(t: T) { // Log it! wrapped.accept(t) }& }º -
151 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :
Consumer<T>¡{ override fun accept(t: T) { // Save it somewhere for later! wrapped.accept(t) }& }º -
1. Simple state reducing 2. Side-effects 3. Events 4. Coupling
5. Scoping 6. Middlewares -
1. Simple state reducing 2. Side-effects 3. Events 5. Coupling
6. Scoping 7. Middlewares https://github.com/badoo/MVICore -
1. MVP 2. Clean architecture 3. MVICore - separate UI
& business logic (somewhat) - separate layers (not the right direction) - solve state-related problems - decouple UI & business logic - standardise business logic 166 -
170 class ComponentImpl( private val params: ScreenParams, news: NewsRelay, onDisposeAction:
() -> Unit, globalFeature: GlobalFeature, conversationControlFeature: ConversationControlFeature, messageSyncFeature: MessageSyncFeature, conversationInfoFeature: ConversationInfoFeature, conversationPromoFeature: ConversationPromoFeature, messagesFeature: MessagesFeature, messageActionFeature: MessageActionFeature, initialScreenFeature: InitialScreenFeature, initialScreenExplanationFeature: InitialScreenExplanationFeature?, errorFeature: ErrorFeature, conversationInputFeature: ConversationInputFeature, sendRegularFeature: SendRegularFeature, sendContactForCreditsFeature: SendContactForCreditsFeature, screenEventTrackingFeature: ScreenEventTrackingFeature, messageReadFeature: MessageReadFeature?, messageTimeFeature: MessageTimeFeature?, photoGalleryFeature: PhotoGalleryFeature?, onlineStatusFeature: OnlineStatusFeature?, favouritesFeature: FavouritesFeature?, isTypingFeature: IsTypingFeature?, giftStoreFeature: GiftStoreFeature?, messageSelectionFeature: MessageSelectionFeature?, reportingFeature: ReportingFeature?, takePhotoFeature: TakePhotoFeature?, giphyFeature: GiphyFeature, goodOpenersFeature: GoodOpenersFeature?, matchExpirationFeature: MatchExpirationFeature, private val pushIntegration: PushIntegration ) : AbstractMviComponent<UiEvent, States>( -
This is fine 171 with(binder) { val uiEvents = combinedUiEvents(view)
val photoUploadHandler = MultiplePhotoUploadHandler( contextWrapper.context as Activity, contextWrapper.lifecycleEventsRx2() ) bind(encounterEvents to encounterAnalyticsTracker) bind(uiEvents to encounterScreenAnalyticsTracker) bind(combinedViewModels to view) bind(voteInitiations to voteController named "SparkDebug.VoteController") bind(voteController to voteTutorialDialogHandler using EncountersEventToShowTu bind(voteTutorialDialogHandler to view using EncountersEventToAnimation) bind(voteTutorialDialogHandler to voteController) bind(voteControllerVotesResolved to voteProcessor using voteEventToVoteAction) bind(voteProcessor to encountersFeature using VoteResultToEncountersWish) bind(encounterEvents to encountersFeature using EncountersEventToEncountersWis bind(uiEvents to modeTabFeature using EncountersScreenEventToModeTabWish) bind(modeTabFeature.news to encountersFeature using ModeTabsNewsToSwitchEncoun bind(encounterEvents to encounterServerStats) bind(blockEvents to BlockOrReportHandler(view, contextWrapper)) bind(matches to matchScreenFeature) bind(uiEvents to photoUploadHandler using EncountersScreenEventToPhotoUploadEv bind(photoUploadHandler to photoStatusFeature using { PhotoStatusFeature.Wish.NotifyPhotoUpload }) bind(locationUpdates to encountersFeature) if (BuildVariant.INFO) { bind(debugCommands to encountersFeature) } } -
178 Benefits • Helps to break down complexity • Encapsulation
of BL + UI • Local navigation • Local DI scope • RIBs + MVICore = -
Additional resources GitHub projects https://github.com/badoo/MVICore https://github.com/badoo/RIBs Article about Features https://badootech.badoo.com/a-modern-kotlin-based-mvi-
architecture-9924e08efab1 Article about Binder https://badootech.badoo.com/building-a-system-of-reactive- components-with-kotlin-ff56981e92cf Time Travel Debugger Demo https://badootech.badoo.com/time-travel-debug-everything- droidconuk-2018-lightning-talk-445217258401