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

Time Travel Debug Everything!

Time Travel Debug Everything!

Time travel debugging is awesome! Remember that obscure bug that QA stumbled across but you could never reproduce? What if you could record and replay events in the app, to see step-by-step how the data changes and why certain actions are executed?

At Badoo, not only do we have a working time travel debugger for our MVI architecture, but we went one step further: our debugger allows you to record and replay interactions everywhere across your reactive subscriptions.

---

The key points were demonstrated on animated slides and a video recording of a demo app, which do not play here. You can check out the video of the presentation:
https://skillsmatter.com/skillscasts/13046-lightning-talk-time-travel-debug-everything

Library & demo app:
https://github.com/badoo/MVICore

Zsolt Kocsi

October 28, 2018
Tweet

More Decks by Zsolt Kocsi

Other Decks in Programming

Transcript

  1. What is time travel debugging? 1 2 3 4 You

    can record and replay execution Not a video recording! Executing the same actions with the same data again Interactive – you can look under the hood
  2. 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) E →I S→VM State
 Store AT View
  3. 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) E →I S→VM State
 Store AT View
  4. 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) E →I S→VM State
 Store AT View
  5. 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) E →I S→VM State
 Store AT View
  6. 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) E →I S→VM State
 Store AT View
  7. 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) E →I S→VM State
 Store AT View
  8. binder.bind(view to analyticsTracker) binder.bind(view to store using EventToIntent) binder.bind(store to

    view using StateToViewModel) // Binder acts as: // // CompositeDisposable // + syntactical sugar // + lifecycle aware // + not tied to Android! E →I S→VM State
 Store AT View
  9. data class State( val counter: Int = 0, val someText:

    String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : Wish() }• class ReducerImpl : Reducer<State, Wish> { }¢ R Fe
  10. data class State( val counter: Int = 0, val someText:

    String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = }¢ R Fe
  11. data class State( val counter: Int = 0, val someText:

    String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { }™ }¢ R Fe
  12. data class State( val counter: Int = 0, val someText:

    String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : 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. data class State( val counter: Int = 0, val someText:

    String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { IncreaseCounter -> state.copy( counter = state.counter + 1 )§ is SetText -> state.copy( someText = wish.text )¡ }™ }¢ R Fe
  14. R Fe class SimpleFeature : ReducerFeature<Wish, State, Nothing>( initialState =

    State(), reducer = ReducerImpl() ) { data class State( val counter: Int = 0, val someText: String = "" )£ sealed class Wish { object IncreaseCounter : Wish() data class SetText(val text: String) : Wish() }• class ReducerImpl : Reducer<State, Wish> { override fun invoke(state: State, wish: Wish): State = when (wish) { IncreaseCounter -> state.copy( counter = state.counter + 1 )§ is SetText -> state.copy( someText = wish.text )¡ }™ }¢ }
  15. binder.bind(view to feature1 using EventToWish1) binder.bind(view to feature2 using EventToWish2)

    binder.bind(view to analyticsTracker) binder.bind(feature2.news to newsListener) val combinedState = combineLatest(feature1, feature2) binder.bind(combinedState to view using StateToViewModel) View NL AT F2 F1
  16. Additional resources 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 GitHub project https://github.com/badoo/MVICore Londroid meetup December ‘18