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

Redux on Android - The nitty gritty

Nish Tahir
August 28, 2018
330

Redux on Android - The nitty gritty

A detailed introduction to the Redux Architecture on Android.

Nish Tahir

August 28, 2018
Tweet

Transcript

  1. Redux on Android, the Nitty Gritty Nish Tahir - @nish_tahir

    Senior Software Engineer, WillowTree Inc. @nish_tahir - #DCNY2018 1 / 47
  2. Cross platform team working on the same app Looking for

    new and better ways to build apps Decided to give Redux/Unidirectional Data Flow a try We ❤ building awesome things together A C B Motivation @nish_tahir - #DCNY2018 2 / 47
  3. App Architecture is a set of guidelines on how to

    structure your code. Clean separation on concerns Single responsibility principle Goals Better state management Make code easier to test Business Logic Model View Presentation App Architecture and You @nish_tahir - #DCNY2018 3 / 47
  4. Application state is data kept in memory State can be

    mutable or immutable Your app renders state on the UI and allows the user to interact with it What is State? @nish_tahir - #DCNY2018 4 / 47
  5. Challenges of State Management Where does state live? Who is

    allowed to modify state? How is state modified? Your app architecture should offer at least some solutions to these problems. @nish_tahir - #DCNY2018 5 / 47
  6. Where does state live? Model, Presenter Who is allowed to

    modify state? Model, Presenter How is state modified? Callbacks, Observables, ... Presenter in response to view Model in response to presenter User Interaction View Update Model Update UI Presenter Model Changed Model Model View Presenter (MVP) @nish_tahir - #DCNY2018 6 / 47
  7. Where does state live? Repository Who is allowed to modify

    state? Interactors How is state modified? Presenters through interactors User Interaction View Fire Event Update UI Presenter Model Changed Update Model Interactor Model Changed Repository Model One Solution? More Layers (MVP) @nish_tahir - #DCNY2018 7 / 47
  8. Data predictably flows in one direction State is encapsulated in

    a single container There is one clear mutation point for state State doesn't "change". New state is created instead Action View Store Dispatcher Unidirectional Data Flow (UDF) @nish_tahir - #DCNY2018 8 / 47
  9. Redux is a UDF implementation created by Dan Abramov. It

    was inspired by Flux and originated on the web. It's built on 3 main principles State is immutable There is single source of truth for state A new state is constructed by pure functions What is Redux @nish_tahir - #DCNY2018 9 / 47
  10. The Redux Flow onEvent Action Dispatcher onNewState Reducer Store State

    Fragments Activities View @nish_tahir - #DCNY2018 10 / 47
  11. State Your app should have one global application state* Single

    source of truth Should be Immutable data class State(val todoList: List<String>) @nish_tahir - #DCNY2018 11 / 47
  12. Modeling State State can contain sub states/properties. Avoid deeply nested

    state Your app state does not need to match the hierarchy of your UI. data class AppState( val home: HomeState, val settings: SettingsState, val items: List<Item> ) @nish_tahir - #DCNY2018 12 / 47
  13. Modeling Behavior into State Expresses how data is expected to

    be used data class AppState( val homeState: HomeState, val settings: SettingsState, val itemsList: Loadable<List<Item>> ) class Loadable<T>(loading: Boolean, value: T?) @nish_tahir - #DCNY2018 13 / 47
  14. Creating New State New state objects can be created by

    shallow copying old states Kotlin copy functions are great for this val newState = oldState.copy( home = oldState.home, items = newItems ) @nish_tahir - #DCNY2018 14 / 47
  15. "Your app should have one global application state*" Navigation state?

    Activity stack Fragment backstack Widget state? Snackbar, Toast, Button Business logic? What should be part of State? @nish_tahir - #DCNY2018 15 / 47
  16. "Your app should have one global application state*" Navigation state?

    Activity stack Fragment backstack Widget state? Snackbar, Toast, Button Business logic? What should be part of State? @nish_tahir - #DCNY2018 16 / 47
  17. Your state should focus on Business logic Let Android deal

    with these Navigation Widgets etc.. What should be part of State? @nish_tahir - #DCNY2018 17 / 47
  18. Actions Represent an intent to move to a new state

    Contain information necessary to make a state transition These should also be Immutable Actions must be unique data class AddTodoAction(val newTodoItem: String) data class UpdateTodoAction(val index: Int, val newTodoItem: String) @nish_tahir - #DCNY2018 20 / 47
  19. Actions as Sealed classes Kotlin makes pattern matching over sealed

    classes really easy. sealed class Action data class AddTodoAction( ... ): Action() data class UpdateTodoAction( ... ): Action() ... when(action) { is AddTodoAction -> ... is UpdateTodoAction -> ... else -> ... } @nish_tahir - #DCNY2018 21 / 47
  20. Reducer Pure function that takes a state and action and

    returns a new state Deterministic - For every set of inputs, there is only one output Only way to generate new state Dang easy to Unit Test fun reducer(oldState: State, action: Action): State { ... } typealias Reducer<T> = (state: T, action: Action) -> T @nish_tahir - #DCNY2018 22 / 47
  21. Given a state data class State(count: Int = 0) And

    an action data class Increment(val value = 1) : Action() Let's see a Reducer in Action @nish_tahir - #DCNY2018 23 / 47
  22. Given a state data class State(count: Int = 0) And

    an action data class Increment(val value = 1) : Action() We can update our reducer to handle this fun reducer(oldState: State, action: Action): State { when(action) { is Increment -> State( oldState.count + action.value ) else oldState } } Let's see a Reducer in Action @nish_tahir - #DCNY2018 24 / 47
  23. How do I Test Reducers? Because Reducers are Pure functions

    and your State and actions are just objects No need for Mocks No need for Instrumentation runners Just plain Unit Tests @Test fun `Test Reducer with given State and Action`() { val testState = State(0) val testAction = Increment(2) val result = reducer(testState, testAction) assertEqual(2, result.count) } @nish_tahir - #DCNY2018 25 / 47
  24. The store ties your State, Actions and Reducers together. Keeps

    a reference to the current application state Invokes the Reducers when actions are dispatched Views (Components) subscribe to the store to be notified of new states Your app should have one store onNewState Reducer Dispatch Action View State Store The Redux Store @nish_tahir - #DCNY2018 26 / 47
  25. Provide an interface for views that can receive state updates

    interface Component { fun onNewState(state: State) } class Store( initialState: State, reducer: Reducer<State> ) { fun getState(): State { ... } fun dispatch(action: Action) { // Invoke middleware and reducer ... } fun subscribe(component: Component) { ... } } The Redux Store in Action @nish_tahir - #DCNY2018 27 / 47
  26. Dispatch actions to the store in response to UI events

    Re-render the view when new state is available class MyCounterActivity: Component { @Inject var store: Store fun onButtonClicked( ... ) { store.dispatch(IncrementAction()) } override fun onNewState(newState: State) { counter_text_view.text = newState.count } } Dispatching Actions to the Store @nish_tahir - #DCNY2018 28 / 47
  27. override fun onNewState(state: State) { // DON'T DO THIS! store.dispatch(IncrementAction())

    } Will result in Infinite loop ♾ Can happen if state updates result in lifecycle events (starting/stoping fragments) View Action onNewState Store Pitfalls Dispatching Actions Dispatching in response to state changes @nish_tahir - #DCNY2018 29 / 47
  28. Asynchronous dispatch/flow Debugging nightmare. Call stacks are lost when switching

    threads. All the problems with Eventbus View Action Dispatcher onNewState Reducer Store State Main Thread Background thread New call stack New call stack Pitfalls Dispatching Actions @nish_tahir - #DCNY2018 30 / 47
  29. But how do I Side Effect? Side effects are things

    that change state outside the scope of your function. Network calls System calls Writing to a Database or Disk @nish_tahir - #DCNY2018 31 / 47
  30. But how do I Side Effect? View Action Dispatcher onNewState

    Reducer Store State Side Effect (Middlewares) Side Effect (Action Creators) @nish_tahir - #DCNY2018 32 / 47
  31. Creates an action to be dispatched Encapsulate side effects before

    dispatching action Great for one-off events like network calls Easy error handling class FetchItemActionCreator @Inject constructor( api: ItemApi ) { fun create( callback: (Action) -> Unit, error: (Throwable) -> Unit ) { // Some asynchronous action ... runOnUiThread { callback(AddTodoItem("Finish my slides")) } } } Action Creators @nish_tahir - #DCNY2018 33 / 47
  32. Intercepts actions in the flow Encapsulates side effects before actions

    are passed to a reducer Great for passive actions such as logging, analytics, persistence Can do everything Action creators can do but errors have to be reflected in state typealias Middleware = ( State, Action, DispatchChain ) -> Unit Middleware @nish_tahir - #DCNY2018 34 / 47
  33. Cause side effects in response to actions Choose to pass

    actions down the chain Or consume an action it's been given and make it go away Middleware Action Middleware Middleware Reducer Middleware Dispatch Chain Any middleware in the chain may @nish_tahir - #DCNY2018 35 / 47
  34. Middleware in Action class AnalyticsMiddleware( client: AnalyticsClient ) : Middleware

    { override fun invoke( state: State, action: Action, chain: DispatchChain ) { when(action) { ... -> { client.ping(" ") chain.next(action) } } } } @nish_tahir - #DCNY2018 36 / 47
  35. Where's the ? There can be a lot of boilerplate

    Everything needs an Action (maybe Action Creator) and handler in the reducer Mistakes can result in infinite loops (Manifests as ANRs with no stacktraces) State copying @nish_tahir - #DCNY2018 37 / 47
  36. Where's the ? There can be a lot of boilerplate

    Everything needs an Action (maybe Action Creator) and handler in the reducer Mistakes can result in infinite loops (Manifests as ANRs with no stacktraces) State copying It's hard making sure actions are dispatched in the right order @nish_tahir - #DCNY2018 38 / 47
  37. Where's the ? There can be a lot of boilerplate

    Everything needs an Action (maybe Action Creator) and handler in the reducer Mistakes can result in infinite loops (Manifests as ANRs with no stacktraces) State copying It's hard making sure actions are dispatched in the right order Debugging asynchronously dispatched actions is hard @nish_tahir - #DCNY2018 39 / 47
  38. Where's the ? There can be a lot of boilerplate

    Everything needs an Action (maybe Action Creator) and handler in the reducer Mistakes can result in infinite loops (Manifests as ANRs with no stacktraces) State copying It's hard making sure actions are dispatched in the right order Debugging asynchronously dispatched actions is hard Learning curve for new team members @nish_tahir - #DCNY2018 40 / 47
  39. Where's the ? There can be a lot of boilerplate

    Everything needs an Action (maybe Action Creator) and handler in the reducer Mistakes can result in infinite loops (Manifests as ANRs with no stacktraces) State copying It's hard making sure actions are dispatched in the right order Debugging asynchronously dispatched actions is hard Learning curve for new team members It's still pretty new on Android @nish_tahir - #DCNY2018 41 / 47
  40. Final Thoughts Redux works best in Apps that are heavily

    state driven - Shelves, Lists, Media players... @nish_tahir - #DCNY2018 42 / 47
  41. Final Thoughts Redux works best in Apps that are heavily

    state driven - Shelves, Lists, Media players... Modeling your data correctly is super important Make your state hierarchy as flat as possible Keep it lightweight and focused on business logic @nish_tahir - #DCNY2018 43 / 47
  42. Final Thoughts Redux works best in Apps that are heavily

    state driven - Shelves, Lists, Media players... Modeling your data correctly is super important Make your state hierarchy as flat as possible Keep it lightweight and focused on business logic Take advantage of things your language (Kotlin ) and platform gives you Don't try to fight the platform for control Use Higher order functions, Data & Sealed classes... @nish_tahir - #DCNY2018 44 / 47
  43. Final Thoughts Redux works best in Apps that are heavily

    state driven - Shelves, Lists, Media players... Modeling your data correctly is super important Make your state hierarchy as flat as possible Keep it lightweight and focused on business logic Take advantage of things your language (Kotlin ) and platform gives you Don't try to fight the platform for control Use Higher order functions, Data & Sealed classes... Use the best tool for your situation Redux is NOT the One True Architecture™ and that's ok. @nish_tahir - #DCNY2018 45 / 47