Effective Reactive Architecture

Effective Reactive Architecture

Video: https://youtu.be/7fE5MDQ_Sc4

Your relationship with RxJava doesn't have to be complicated. You may have found many ways to use it wrong but it is powerful when used properly.In fact, RxJava can guide you in shaping a sound architecture for your app. To do this, you only have to follow a few but decisive principles.

In this talk, you'll:
- Explore how side-effect isolation can help avoiding bugs.
- Learn how to model your data and share it as one unique stream between your view and your presenter.
- Discover the ways a unidirectional data flow makes adding new functionality easy and safe.
- Look at how you can easily deal with collateral side effects, such as navigation.

After this talk, you’ll be able to write a robust and reactive architecture for your app, taking full advantage of RxJava.

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

October 24, 2019
Tweet

Transcript

  1. 2.
  2. 4.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { } }
  3. 5.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { nameSubject .switchMap { name -> appService.profile() .map { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) } }
  4. 6.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { nameSubject .switchMap { name -> appService.profile() .map { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) } }
  5. 7.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { nameSubject .switchMap { name -> appService.profile() .map { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } }
  6. 8.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { nameSubject .switchMap { name -> appService.profile() .map { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } }
  7. 9.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { nameSubject .switchMap { name -> appService.profile() .map { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) }
  8. 10.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) } }
  9. 11.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  10. 12.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  11. 13.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  12. 14.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  13. 15.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  14. 16.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  15. 17.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  16. 18.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  17. 19.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  18. 20.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  19. 21.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  20. 22.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  21. 23.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  22. 24.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  23. 25.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  24. 26.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  25. 27.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  26. 28.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  27. 29.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  28. 30.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  29. 31.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  30. 32.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  31. 33.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  32. 34.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  33. 35.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  34. 36.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  35. 37.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  36. 38.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  37. 39.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  38. 40.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  39. 41.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  40. 42.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  41. 43.
  42. 45.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  43. 46.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  44. 47.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  45. 48.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  46. 49.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  47. 50.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  48. 51.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  49. 52.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  50. 53.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  51. 54.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  52. 55.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  53. 56.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  54. 57.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  55. 58.

    fun onStart() { nameSubject .switchMap { name -> appService.profile() .map

    { profile -> profile.prefix } .map { profile -> if (name.isEmpty()) "" else "$prefix $name" } } .subscribe(nameView::setText) // hint grows the view in width so hide it if unnecessary nameSubject .map { name -> if (name.isEmpty()) "Set your name" else "" } .subscribe(nameView::setHint) appService.profile().subscribe(this::populateProfile) } fun populateProfile(profile: Profile) { nameSubject.onNext(profile.name) // rendering other stuff } fun onSetNameDialogResult(newName: String) = setName(newName) fun setName(newName: String) { nameSubject.onNext(newName) appService.setName(newName) .subscribe { response -> when { response.isValid -> nameSubject.onNext(response.name) response.isInvalid -> navigator.goTo(response.error) } } }
  56. 59.

    class ProfileView { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { } }
  57. 60.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { } }
  58. 62.

    sealed class Result { data class ProfileResult(val profile: Profile) :

    Result() data class NameValid(val name: String) : Result() object NameInvalid : Result() }
  59. 64.

    data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName )

    { data class PrefixedName( val prefix: String, val name: String ) { fun compute(name:String): String = if (name.isEmpty()) "" else "$prefix $name" } }
  60. 65.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() fun onStart() { } }
  61. 66.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> } }
  62. 67.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> .subscribe { viewModel -> } } }
  63. 68.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  64. 69.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> } ) } .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  65. 70.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } } ) } .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  66. 71.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } }
  67. 72.

    class ProfileViewRemix { val navigator: Navigator val nameView: TextView val

    appService: AppService val nameSubject = BehaviorSubject.create<String>() val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  68. 73.

    val nameView: TextView val appService: AppService val nameSubject = BehaviorSubject.create<String>()

    val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  69. 74.

    val nameView: TextView val appService: AppService val nameSubject = BehaviorSubject.create<String>()

    val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  70. 75.

    val nameView: TextView val appService: AppService val nameSubject = BehaviorSubject.create<String>()

    val events = PublishSubject.create<Event>() fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  71. 76.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } }
  72. 77.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff }
  73. 78.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  74. 79.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  75. 80.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  76. 81.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  77. 82.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  78. 83.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  79. 84.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  80. 85.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  81. 86.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  82. 87.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  83. 88.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  84. 89.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  85. 90.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  86. 91.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  87. 92.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  88. 93.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  89. 94.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  90. 95.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  91. 96.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  92. 97.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  93. 98.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  94. 99.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  95. 100.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  96. 101.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  97. 102.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  98. 103.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  99. 104.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  100. 105.
  101. 107.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  102. 108.

    fun onStart() { events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap

    { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) .subscribe { viewModel -> nameView.hint = if (viewModel.prefixedName.name.isEmpty()) "Set your name" else "" nameView.text = viewModel.prefixedName.compute() // rendering other stuff } } fun onSetNameDialogResult(newName: String) { events.onNext(EditName(newName)) }
  103. 111.
  104. 115.

    events .subscribe { viewModel -> } class ProfilePresenter : Consumer<Event>

    { override fun accept(event: Event) { } } View Presenter
  105. 117.

    class ProfilePresenter : Consumer<Event> { override fun accept(event: Event) {

    event.flatMap {} } } View Presenter events.subscribe(presenter) presenter.something().subscribe { viewModel -> }
  106. 120.
  107. 123.

    presenter.viewModels(events) .subscribe { viewModel -> } class ProfilePresenter { fun

    viewModels(events: Observable<Event>) { events.publish { events -> } } } View Presenter
  108. 124.
  109. 127.

    presenter.viewmodels() .subscribe { viewModel -> } class ProfilePresenter { fun

    viewmodels(): Observable<ViewModel> { } } View Presenter
  110. 129.

    presenter .subscribe { viewModel -> } class ProfilePresenter: Observable<ViewModel>() {

    override fun subscribeActual(observer: Observer<in ViewModel>) { } } View Presenter
  111. 132.

    presenter .subscribe(object : Observer<ViewModel> { override fun onComplete() {} override

    fun onSubscribe(d: Disposable) {} override fun onNext(t: ViewModel) {} override fun onError(e: Throwable) {} }) class ProfilePresenter: ObservableSource<ViewModel> { override fun subscribe(observer: Observer<in ViewModel>) { } } View Presenter
  112. 133.

    Observable.wrap(presenter) .subscribe { viewModel -> } class ProfilePresenter: ObservableSource<ViewModel> {

    override fun subscribe(observer: Observer<in ViewModel>) { } } View Presenter
  113. 134.
  114. 139.

    interface ObservableTransformer<Upstream, Downstream> { /** * Applies a function to

    the upstream Observable and returns an ObservableSource with * optionally different element type. */ fun apply(upstream: Observable<Upstream>): ObservableSource<Downstream> }
  115. 145.

    class Presenter: ObservableTransformer<Event, ViewModel> { fun apply(events: Observable<Event>): ObservableSource<ViewModel> }

    val testObserver = events // PublishSubject<Event> .compose(presenter) .test() // TestObserver<ViewModel> Test Presenter
  116. 146.

    class Presenter: ObservableTransformer<Event, ViewModel> { fun apply(events: Observable<Event>): ObservableSource<ViewModel> }

    val testObserver = events // PublishSubject<Event> .compose(presenter) .test() // TestObserver<ViewModel> events.onNext(EditName("Georges")) testObserver.assertValue(ViewModel()) Test Presenter
  117. 147.

    Presenter • Don't break the stream • Don't start work

    until observed • Doesn't handle disposables* • Has one entry point • Has one exit point
  118. 148.
  119. 152.

    class ProfilePresenter { val events = PublishSubject.create<Event>() fun passEvents(events: Observable<Event>)

    { events.subscribe(this.events) } fun viewModels() { return events.businessLogic() } } View Presenter
  120. 153.

    class ProfilePresenter { val events = PublishSubject.create<Event>() fun passEvents(events: Observable<Event>)

    { events.subscribe(this.events) } private val vmObservable : Observable<MatchesViewState> by lazy(NONE) { events.businessLogic() .replay(1) .autoConnect(0) } fun viewModels() = vmObservable } View Presenter
  121. 154.

    presenter.viewModels() .subscribe { viewModel -> } View Presenter class ProfilePresenter

    { val events = PublishSubject.create<Event>() fun passEvents(events: Observable<Event>) { events.subscribe(this.events) } private val vmObservable : Observable<MatchesViewState> by lazy(NONE) { events.businessLogic() .replay(1) .autoConnect(0) } fun viewModels() = vmObservable }
  122. 155.

    presenter.viewModels() .subscribe { viewModel -> } presenter.passEvents(events) View Presenter class

    ProfilePresenter { val events = PublishSubject.create<Event>() fun passEvents(events: Observable<Event>) { events.subscribe(this.events) } private val vmObservable : Observable<MatchesViewState> by lazy(NONE) { events.businessLogic() .replay(1) .autoConnect(0) } fun viewModels() = vmObservable }
  123. 156.
  124. 169.
  125. 171.

    sealed class Event { data class EditName(val newName: String) :

    Event() } data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName ) class ProfileViewRemix { fun onStart() { events .compose(presenter) .subscribe { viewModel -> // rendering view model } } }
  126. 172.

    sealed class Event { data class EditName(val newName: String) :

    Event() } data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName ) class ProfileViewRemix { fun onStart() { events .compose(presenter) .subscribe { viewModel -> // rendering view model } } }
  127. 173.

    sealed class Event { data class EditName(val newName: String) :

    Event() data class ChangeAvatar(val picture: File) : Event() } data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName ) class ProfileViewRemix { fun onStart() { events .compose(presenter) .subscribe { viewModel -> // rendering view model } } }
  128. 174.

    sealed class Event { data class EditName(val newName: String) :

    Event() data class ChangeAvatar(val picture: File) : Event() } data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName, val avatarUri: Uri ) class ProfileViewRemix { fun onStart() { events .compose(presenter) .subscribe { viewModel -> // rendering view model } } }
  129. 175.

    sealed class Event { data class EditName(val newName: String) :

    Event() data class ChangeAvatar(val picture: File) : Event() } data class ViewModel( val stuff: Any?, val prefixedName: PrefixedName, val avatarUri: Uri ) class ProfileViewRemix { fun onStart() { events .compose(presenter) .subscribe { viewModel -> // rendering view model Picasso.get().load(viewModel.avatarUri).into(avatarView) } } }
  130. 176.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>() .switchMap { event -> appService.setName(event.newName).map { response -> when { response.isValid -> NameValid(response.name) response.isInvalid -> NameInvalid.also { navigator.goTo(response) } } } .startWith(NameValid(event.newName)) } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) } }
  131. 177.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>().logic() ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) } }
  132. 178.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>().logic() events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) } }
  133. 179.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>().logic() events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } .onErrorReturn { throwable -> AvatarInvalid(throwable) } } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) } }) } }
  134. 180.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>().logic() events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } .onErrorReturn { throwable -> AvatarInvalid(throwable) } } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) is AvatarValid -> previous.copy(avatarUri = result.uri) } }) } }
  135. 181.

    class ProfilePresenter : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>):

    ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<EditName>().logic() events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } .onErrorReturn { throwable -> AvatarInvalid(throwable) } } ) } .mergeWith(appService.profile().map(::ProfileResult)) .scan(ViewModel(stuff = null, prefixedName = PrefixedName("", "")), BiFunction { previous: ViewModel, result: Result -> when (result) { is NameValid -> previous.copy(prefixedName = previous.prefixedName.copy(name = result.name)) is NameInvalid -> previous is ProfileResult -> previous.copy( stuff = result.profile, prefixedName = PrefixedName(result.profile.prefix, result.profile.name) ) is AvatarValid -> previous.copy(avatarUri = result.uri) is AvatarInvalid -> TODO() } }) } }
  136. 182.
  137. 186.

    Architecture forced us to separate concerns No need to touch

    existing code Easy to test Easy to review
  138. 187.
  139. 190.

    events.filterIsInstance<ChangeAvatar>().changeAvatar() private fun Observable<ChangeAvatar>.changeAvatar(): Observable<Result> { return switchMap { event

    -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } } } events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } }
  140. 191.

    events.filterIsInstance<ChangeAvatar>().changeAvatar() private fun Observable<ChangeAvatar>.changeAvatar(): Observable<Result> { return switchMap { event

    -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } } } events.filterIsInstance<ChangeAvatar>() .switchMap { event -> appService.changeAvatar(event.picture) .map { response -> AvatarValid(response.uri) } }
  141. 192.
  142. 193.
  143. 195.

    interface Navigator { fun goTo(screen: Any) } interface Launcher {

    fun shareText(text: String): Boolean fun launchUrl(url: String): Boolean fun launchApp(packageName: String): Boolean }
  144. 197.

    interface Navigator { fun goTo(screen: Any) } class ProfilePresenter( private

    val navigator: Navigator ) : ObservableTransformer<Event, ViewModel> { }
  145. 198.

    interface Navigator { fun goTo(screen: Any) } class ProfilePresenter( private

    val navigator: Navigator ) : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>): ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<GoToEvent>().goTo() ) } } }
  146. 199.

    interface Navigator { fun goTo(screen: Any) } class ProfilePresenter( private

    val navigator: Navigator ) : ObservableTransformer<Event, ViewModel> { override fun apply(events: Observable<Event>): ObservableSource<ViewModel> { return events.publish { events -> Observable.merge( events.filterIsInstance<GoToEvent>().goTo() ) } } private fun Observable<GoToEvent>.goTo(): Observable<ViewModel> { return doOnNext { navigator.goTo(SomeScreen) } .ignoreElements() .toObservable() } }
  147. 200.

    inline fun <T : Any, R : Any> Observable<T>.consumeOnNext( crossinline

    sideEffect: (T) -> Unit ): Observable<R> { return doOnNext { sideEffect(it) } .ignoreElements() .toObservable() } private fun Observable<GoToEvent>.goTo(): Observable<ViewModel> { return consumeOnNext { navigator.goTo(SomeScreen) } }
  148. 201.

    class FakeNavigator : Navigator { private val navigatedScreens = ArrayDeque<Any>()

    override fun goTo(screen: Any) { navigatedScreens.add(screen) } fun takeNext() = navigatedScreens.pop()!! fun isEmpty() = navigatedScreens.isEmpty() } events.accept(Event) assertThat(navigator.takeNextScreen()).isEqualTo(ExpectedScreen)
  149. 202.