Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Architectural Journey

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
Tweet

More Decks by Zsolt Kocsi

Other Decks in Programming

Transcript

  1. 1. MVP
 2. Clean architecture 3. ??? What we need

    Immutable state How to do it best ??? 14
  2. 1. MVP
 2. Clean architecture 3. Proto-MVI Inspired by Redux

    Frankenstein’s monster of • MVP • Clean architecture • Immutable state stores • RxJava 15
  3. 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
  4. 1. MVP
 2. Clean architecture 3. Proto-MVI 4. Experiments
 5.

    MVICore https://github.com/badoo/MVICore 18
  5. 22 data class State( val counter: Int = 0 )£

    sealed class Wish { object IncreaseCounter : Wish() data class MultiplyBy(val value: Int) : Wish() }• R Fe
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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 )¡ }™ }¢ }
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 45 data class State( val isLoading: Boolean = false, valªhasError:ªBoolean

    = false, val payload: Any? = null )å≈ R Fe Ac
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 61 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, Nothing>( initialState =

    State(), actor = ActorImpl(), reducer = ReducerImpl() ) R Fe Ac
  36. 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 )
  37. 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 )
  38. 72 News Publ. Reducer Feature Actor Wish State Effect News

    State machines Reducer only Side-effects with Actor Events
  39. 73 News Publ. Reducer Feature Actor Wish State Effect News

    State machines Reducer only Side-effects with Actor Events
  40. 74 Reducer Feature Actor News Publ. Wish State Effect News

    State machines Reducer only Side-effects with Actor Events
  41. 75 Feature News Publ. Reducer Actor Wish State Effect News

    State machines Reducer only Side-effects with Actor Events
  42. 76 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, Nothing>( initialState =

    State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N
  43. 77 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, >( initialState =

    State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N
  44. 78 class AsyncFeature : ActorReducerFeature<Wish, Effect, State, News>( initialState =

    State(), actor = ActorImpl(), reducer = ReducerImpl() )π F N R A W S E N
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 91 F V Y X Pros Easy Cons No single-source

    of truth Connecting components Data-flow
  53. 95 Pros Single source of truth! Better control over flow

    F V Y X Connecting components Data-flow
  54. 96 Pros Single source of truth! Better control over flow

    F V Connecting components Data-flow
  55. 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) }ƒ
  56. 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); }
  57. 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; }
  58. 102 interface Feature : Consumer<Wish>, ObservableSource<State> { sealed class Wish

    data class State }£ interface View : Consumer<State>, ObservableSource<Wish> { }¡
  59. 105 interface Feature : Consumer<Wish>, ObservableSource<State> { sealed class Wish

    data class State } interface View : Consumer<State>, ObservableSource<Wish> { }
  60. 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 }
  61. 107 F V Pros No coupling :) Cons ? Connecting

    components Data-flow Coupling
  62. 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
  63. 109 Pros No coupling :) Cons Not flexible :( F

    V F ??? Connecting components Data-flow Coupling
  64. 110 F F ??? Pros No coupling :) Cons Not

    flexible :( Connecting components Data-flow Coupling
  65. 111 F V X Y ??? Pros No coupling :)

    Cons Not flexible :( Connecting components Data-flow Coupling
  66. 115 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  67. 116 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  68. 117 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  69. 118 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  70. 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)
  71. 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)
  72. 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)
  73. 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)
  74. 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)
  75. 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)
  76. 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)
  77. 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)
  78. 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)
  79. 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)
  80. 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)
  81. 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)
  82. 132 C S Lifecycle sources Android / Create-Destroy Android /

    Start-Stop Android / Resume-Pause From Observable Manual Connecting components Data-flow Coupling Scoping
  83. 133 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  84. 135 F V V X Y F V Connecting components

    Data-flow Coupling Scoping
  85. 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)
  86. 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; }
  87. 147 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :

    Consumer<T>¡{ override fun accept(t: T) { }& }º
  88. 148 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :

    Consumer<T>¡{ override fun accept(t: T) { wrapped.accept(t) }& }º
  89. 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 }& }º
  90. 150 abstract class Middleware<T>( private val wrapped: Consumer<T> ) :

    Consumer<T>¡{ override fun accept(t: T) { // Log it! wrapped.accept(t) }& }º
  91. 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) }& }º
  92. 1. Simple state reducing 2. Side-effects 3. Events 5. Coupling

    6. Scoping 7. Middlewares https://github.com/badoo/MVICore
  93. 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
  94. 168

  95. 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>(
  96. 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) } }
  97. 173

  98. 178 Benefits • Helps to break down complexity • Encapsulation

    of BL + UI • Local navigation • Local DI scope • RIBs + MVICore =
  99. 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