Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

3

Share

Dispatching Reactive State

Download to read offline

Droidcon Italy 2018 Slides - Dispatching Reactive State

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Dispatching Reactive State

  1. 1. UI Navigation Mike Nakhimovich
  2. 2. Dispatching State Mike Nakhimovich
  3. 3. Apps have a UI
  4. 4. Apps have a UI Search View
  5. 5. Apps have a UI Search View ResultsView
  6. 6. Apps have a UI Search View ResultsView ErrorView
  7. 7. Apps have a UI Search View ResultsView ErrorView LoadingView
  8. 8. Users interact with the UI Pizza
  9. 9. Interactions lead to State change Pizza
  10. 10. Pizza Interactions lead to State change
  11. 11. PizzaPizza State change need to be rendered
  12. 12. What is state?
  13. 13. A representation of UI at a certain point in time What is state?
  14. 14. State determines how the UI renders & behaves
  15. 15. Android lacks patterns for working with state & views
  16. 16. How to create state
  17. 17. How to pass state
  18. 18. How to hold state
  19. 19. How do we think about state?
  20. 20. data class ScreenState( val searchResults:List<Results>, val searchTerm:String, val isLoading:Boolean) Example: state modeled as a single data class
  21. 21. data class ScreenState( val searchResults:List<Results>, val searchTerm:String, val isLoading:Boolean, val CartItems:List<Items>, val userId:Long Example: state modeled as a single growing data class
  22. 22. data class ScreenState( val searchResults:List<Results>, val searchTerm:String, val isLoading:Boolean, val CartItems:List<Items>, val userId:Long State modeled as a God class
  23. 23. Android devs love being reactive, why not represent state as a stream?
  24. 24. Lately, I’ve been using a centralized dispatcher to model state as a reactive stream
  25. 25. What’s a dispatcher?
  26. 26. A dispatcher is an object that receives and sends messages about state
  27. 27. that the UI reacts to A dispatcher is an object that receives and sends messages about state
  28. 28. Why a state dispatcher?
  29. 29. Decouple producers of state change from its consumers Why a state dispatcher?
  30. 30. Journey to dispatcher
  31. 31. User types a search term Pizza
  32. 32. User clicks search Pizza
  33. 33. Loading should become visible Pizza
  34. 34. Followed by results Pizza
  35. 35. Pizza Proposal 1: Encapsulate Screen in a ScreenPresenter How do we tell views what to display?
  36. 36. PizzaScreen Presenter SearchView ResultsView Pizza Proposal 1: Encapsulate Screen in a ScreenPresenter How do we tell ResultsViews what to display?
  37. 37. Pizza But then… Product adds another view PizzaScreen Presenter SearchView ResultsViewLoadingView
  38. 38. And then… Product adds 3 more views Pizza PizzaScreen Presenter SearchView ResultsViewLoading ProfileTab DrawerViewCartView
  39. 39. Proposal 1: Create a ScreenPresenter which contains both Presenters How do we tell ResultsView what to display? Pizza Not very scalable
  40. 40. How do we tell ResultsView what to display? Pizza Proposal 2: Create a Listener
  41. 41. Proposal 2: Create a Listener Pizza SearchViewResultsView Listens To
  42. 42. Proposal 2: Create a Listener SearchViewResultsView Listens To LoadingView ListensTo Listens To
  43. 43. Proposal 2: Create a Listener Listeners lead to circular dependencies
  44. 44. Last Proposal Use a centralized dispatcher for ALL state change
  45. 45. A Dispatcher is responsible for receiving state changes and emitting them Use a centralized dispatcher for ALL state change
  46. 46. Why a state dispatcher?
  47. 47. Views become coupled to the dispatcher not each other SearchViewResultsView LoadingView Dispatcher
  48. 48. Reactive state UI creates new state
  49. 49. Reactive state UI reacts to new state
  50. 50. Reactive state With no hard references between UI elements
  51. 51. Reactive state Push rather than pull
  52. 52. Implementing reactive state
  53. 53. Reactive state Sealed class State{ data class ShowingLoading():State data class ShowingResults(val results:List<String>):State data class ShowingError(val error:Error):State } Represent your state as immutable value objects
  54. 54. Reactive state Push new state through a reactive stream Interface Dispatcher fun dispatch(state: State) } interface RxState { fun <T> ofType(clazz: Class<T>): Observable<T> } Sealed class State{ data class ShowingLoading():State data class ShowingResults(val results:List<String>):State data class ShowingError(val error:Error):State }
  55. 55. Reactive state Anytime UI needs to change, dispatch a new state dispatcher.dispatch(State.ShowingResults(resultData) dispatcher.dispatch(State.ShowingLoading() dispatcher.dispatch(State.ShowingError(errors)
  56. 56. Reactive state Anytime state changes, react to new state rxState.ofType(State.Results) .map{it.data} .subscribe{ mvpView.updateUI(data) }
  57. 57. Rather than tightly coupling Search & Results we decouple them with a dispatcher
  58. 58. Reactive state visualized Dispatcher
  59. 59. Reactive state ResultsPresenter Subscribes to ShowResults states change Dispatcher ResultsPresenter
  60. 60. Reactive state LoadingPresenter Subscribes to ShowLoading states change Dispatcher ResultsPresenter LoadingPresenter
  61. 61. Reactive state Dispatcher SearchPresenterSearchView SearchTerm
  62. 62. Reactive state Dispatcher SearchPresenterSearchView SearchTerm ShowingLoading
  63. 63. Reactive state Dispatcher SearchPresenterSearchView SearchTerm ShowingLoading
  64. 64. Reactive state Dispatcher SearchPresenterSearchView SearchTerm ShowingLoading
  65. 65. Reactive state Dispatcher SearchPresenterSearchView SearchTerm ShowingLoading LoadingPresenter
  66. 66. Reactive state Dispatcher SearchPresenterSearchView SearchTerm ShowingLoading LoadingPresenter
  67. 67. Reactive state Dispatcher SearchPresenterSearchView SearchTerm
  68. 68. Reactive state Search Presenter calls dispatcher.dispatch(State.ShowingResults(resultData) Dispatcher SearchPresenterSearchView SearchTerm ShowingResults
  69. 69. Reactive state Dispatcher needs to emit a new State to subscribers Dispatcher SearchPresenter ResultsPresenter SearchView SearchTerm ShowingResults
  70. 70. Reactive state Dispatcher Emits new ShowingResults State to ResultsPresenter Dispatcher SearchPresenter ResultsPresenter SearchView SearchTerm ShowingResults
  71. 71. Reactive state Dispatcher Emits new ShowingResults State to subscribers Dispatcher SearchPresenterSearchView SearchTerm ShowingResults ResultsPresenter
  72. 72. Reactive state Dispatcher Emits new ShowingResults State to subscribers Dispatcher SearchPresenter ResultsPresenter SearchView SearchTerm ResultsView ShowingResults
  73. 73. Reactive state Multiple Views can listen to same state change Dispatcher SearchPresenterSearchView SearchTerm ResultsPresenterResultsView LoadingPresenterLoadingView
  74. 74. Reactive state Dispatcher SearchPresenterSearchView ShowingResults LoadingPresenterLoadingView ResultsPresenterResultsView
  75. 75. Reactive state Dispatcher SearchPresenterSearchView ShowingResultsShowingResults LoadingPresenterLoadingView ResultsPresenterResultsView
  76. 76. Reactive state Dispatcher SearchPresenterSearchView ShowingResults ShowingResults LoadingPresenterLoadingView ResultsPresenterResultsView
  77. 77. Reactive state Dispatcher SearchPresenterSearchView ShowingResults ShowingResults LoadingPresenterLoadingView ResultsPresenterResultsView
  78. 78. Reactive state Dispatcher SearchPresenterSearchView ShowResults Hide View LoadingPresenterLoadingView ResultsPresenterResultsView
  79. 79. Interacting with a dispatcher
  80. 80. Pizza override fun attachView(view: NearYouMVPView) { rxState.ofType(State.Results) .map{it.data} .subscribe{ mvpView.updateUI(data) } } Presenter subscribes on attach
  81. 81. Another presenter dispatches state change Pizza fun searchFor(searchTerm:String){ store.getResults(searchTerm) .subscribe{dispatcher.dispatch(State.Results(data=it.results)}
  82. 82. Dispatcher emits state change Pizza fun searchFor(searchTerm:String){ store.getResults(searchTerm) .subscribe{dispatcher.dispatch(State.Results(data=it.results)} override fun attachView(view: NearYouMVPView) { rxState.ofType(State.Results) .map{it.data} .subscribe{ mvpView.updateUI(data) } }
  83. 83. Consumers & Producers of state stay decoupled Pizza fun searchFor(searchTerm:String){ store.getResults(searchTerm) .subscribe{dispatcher.dispatch(State.Results(data=it.results)} override fun attachView(view: NearYouMVPView) { rxState.ofType(State.Results) .map{it.data} .subscribe{ mvpView.updateUI(data) } }
  84. 84. How does data flow?
  85. 85. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView
  86. 86. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults1
  87. 87. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults1
  88. 88. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults1
  89. 89. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults1
  90. 90. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults2
  91. 91. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults2
  92. 92. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults2
  93. 93. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView ShowingResults2
  94. 94. Reactive state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenter
  95. 95. More state Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenter Add To Cart
  96. 96. Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenter Add To Cart More state
  97. 97. Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenter Add To Cart More state
  98. 98. Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenter Add To Cart More state
  99. 99. Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenterCartView Add To Cart More state
  100. 100. Dispatcher SearchPresenterSearchView ResultsPresenterResultsView CartPresenterCartView Add To Cart More state
  101. 101. Emitting data is nice… How about showing/hiding views?
  102. 102. When user clicks on cart, we should show the cart view HomeScreen SearchView ResultsViewCartView
  103. 103. How?
  104. 104. We can combine state changes through composition
  105. 105. Showing states sealed class State { object Cart : State() data class Showing(val state: State) : State() }
  106. 106. Showing states dispatcher.dispatch(State.Showing(Cart)) sealed class State { object Cart : State() data class Showing(val state: State) : State() }
  107. 107. Showing states rxState.showing(Cart::class.java)) .subscribe({ mvpView.showCart() })) interface RxState { fun showingNot(clazz: Class<out Screen>): Observable<Showing> fun showing(clazz: Class<out Screen>): Observable<Showing> }
  108. 108. We can show/hide views based on showing states rxState.showingNot(Cart::class.java)) .subscribe({ mvpView.hide() })) rxState.showing(Cart::class.java)) .subscribe({ mvpView.showCart() }))
  109. 109. Dispatching showing cart rxState.showingNot(Cart::class.java)) .subscribe({ mvpView.hide() })) dispatcher.dispatch(State.Showing(Cart)) rxState.showing(Cart::class.java)) .subscribe({ mvpView.showCart() }))
  110. 110. Dispatching Showing Checkout Checkout rxState.showingNot(Cart::class.java)) .subscribe({ mvpView.hide() })) dispatcher.dispatch(State.Showing(Checkout)) rxState.showing(Cart::class.java)) .subscribe({ mvpView.showCart() }))
  111. 111. Showing/hiding works well when all views attached
  112. 112. Not very scalable in real apps Showing/hiding works well when all views attached
  113. 113. How do we deal with deep flows?
  114. 114. How do we deal with deep flows? Treat screen creation/routing as a state change
  115. 115. Dispatching screens workflow
  116. 116. Checkout CartPresenterCartView
  117. 117. Checkout CartPresenterCartView Go To Checkout
  118. 118. Checkout Screen.Checkout CartPresenterCartView
  119. 119. Checkout Screen.Checkout CartPresenterCartView
  120. 120. Checkout ScreenCreator CartPresenterCartView Creating(Screen.Checkout)
  121. 121. Checkout ScreenCreator CartPresenterCartView Creating(Screen.Checkout)
  122. 122. Hidden CheckoutVIew CartPresenterCartView Screen.Checkout ScreenCreator CheckoutPresenter CheckoutView
  123. 123. Hidden CheckoutVIew CartPresenterCartView Showing(Screen.Checkout) ScreenCreator CheckoutPresenter CheckoutView
  124. 124. Hidden CheckoutVIew CartPresenterCartView Showing(Screen.Checkout) ScreenCreator CheckoutPresenter CheckoutView
  125. 125. Hidden CheckoutVIew CartPresenterCartView Showing(Screen.Checkout) ScreenCreator CheckoutPresenter CheckoutView
  126. 126. CheckoutView With Data CartPresenterCartView Showing(Screen.Checkout) ScreenCreator CheckoutPresenter CheckoutView
  127. 127. View tells presenter to go to new screen presenter.openCart()
  128. 128. Presenter dispatches new screen state sealed class Screen : State() { data class Checkout(val payload:String) : Screen() } dispatcher.dispatch(Screen.Checkout)
  129. 129. Dispatcher encapsulates in a creating state dispatcher.dispatch(Creating(Screen.Checkout))
  130. 130. Screen creator creates view/presenter rxState.creating() .subscribe({ createScreen(it) })
  131. 131. Dispatches a showing state rxState.creating() .subscribe({ createScreen(it) dispatcher.dispatch(Showing(it.screen)) })
  132. 132. Dispatcher adds each showing event to a back stack override fun dispatch(state: State) { when (state) { is Showing->{ backstack.push(state) rxState.push(state) } else -> rxState.push(state) } }
  133. 133. Dispatcher adds each showing event to a back stack override fun dispatch(state: State) { when (state) { is Showing->{ backstack.push(state) rxState.push(state) } else -> rxState.push(state) } } val backstack: Stack<Showing> = Stack()
  134. 134. Dispatcher pushes new state to subscribers override fun dispatch(state: State) { when (state) { is Showing->{ backstack.push(state) rxState.push(state) } else -> rxState.push(state) } }
  135. 135. Presenter reacts to the showing state override fun attachView(mvpView: CheckoutMVPView) { rxState.showing(Screen.Checkout::class.java) .observeOn(AndroidSchedulers.mainThread()) .subscribe { mvpView.show() }
  136. 136. Screens = States sealed class Screen : State() { object Search : Screen() object Cart:Screen() Data class Checkout(val items:List<Item>) : Screen() data class Payment(val items:List<Item>) : Screen() }
  137. 137. Routing becomes a state change sealed class Screen : State() { object Search : Screen() object Cart:Screen() Data class Checkout(val items:List<Item>) : Screen() data class Payment(val items:List<Item>) : Screen() } fun goToSearch() {dispatcher.dispatch(Screen.Search)} fun openCart() { dispatcher.dispatch(Screen.Cart) } fun submitNames(firstName: String, lastName: String) { userRepository.update(firstName, lastName) .subscribe { dispatcher.dispatch(Screen.CheckoutPayment(cartItems)) } }
  138. 138. Need to go back?
  139. 139. Dispatcher.goBack() override fun onBackPressed() { dispatcher.goBack() } interface Dispatcher { fun dispatch(state: State) fun goBack() }
  140. 140. Dispatcher.goBack() Dispatcher will pop current showing state and dispatch previous one again interface Dispatcher { fun dispatch(state: State) fun goBack() } override fun onBackPressed() { dispatcher.goBack() }
  141. 141. State Stack = Back Stack override fun onBackPressed() { dispatcher.goBack() } interface Dispatcher { fun dispatch(state: State) fun goBack() }
  142. 142. Back Stack = State Stack interface Dispatcher { fun dispatch(state: State) fun goBack() } override fun onBackPressed() { dispatcher.goBack() }
  143. 143. Routing becomes a state change
  144. 144. Every view interaction becomes a state change
  145. 145. Your UI becomes a representation of dispatched state
  146. 146. Which is dispatched by your UI
  147. 147. TLDR: Your app becomes a cycle
  148. 148. TLDR: Your app becomes a cycle User interacts with view
  149. 149. TLDR: Your app becomes a cycle User interacts with view View dispatches state change
  150. 150. TLDR: Your app becomes a cycle User interacts with view View dispatches state change View reacts to state change
  151. 151. TLDR: Your app becomes a cycle User interacts with view View dispatches state change View reacts to state change User interacts with view
  152. 152. github.com/ digitalbuddha/Dispatcher Big thank you to Efeturi Money for the UI
  • twitchmeister

    Jun. 13, 2018
  • AxelCamilo1

    Apr. 21, 2018
  • AndreaRinaldi5

    Apr. 20, 2018

Droidcon Italy 2018 Slides - Dispatching Reactive State

Views

Total views

885

On Slideshare

0

From embeds

0

Number of embeds

8

Actions

Downloads

22

Shares

0

Comments

0

Likes

3

×