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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
} 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
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
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
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
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
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
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
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
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
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
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
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