Borrowing the best of the web to make native better Brandon Kase & Christina Lee
Take 1:boss asks for friend buttonwe say, 'OK! We'll do it this week!'we do not finish it in a weekwoah...this is very complicated!boss is less than thrilled
Take 2:boss asks for friend buttonwe say, 'OK! We'll do it this week!'we modify our approachwe do not finish it in a weekwe finish it in a day!boss is thrilledBrandon and Christina still have jobs, hurray!
What went wrongFetch data before view transitionsOptimistically update componentsSend server requests and react to responses
As developers, we are expected to handleoptimistic updates, server- side rendering,
fetching data before performing routetransitions, and so on... This complexity is
difficult to handle as we’re mixing twoconcepts that are very hard for the human
mind to reason about: mutation andasynchronicity. I call them Mentos and Coke”
Motivation | Redux
Web AlliesThe web faces all of these challenges and moreUnlike native; however, it's iteration rate is fastNot "one" UI framework
Redux// The current application state (list of todos and chosen filter) let previousState = { visibleTodoFilter: 'SHOW_ALL', todos: [ { text: 'Read the docs.', complete: false } ] }
// The action being performed (adding a todo) let action = { type: 'ADD_TODO', text: 'Understand the flow.' }
// Your reducer returns the next application state let nextState = todoApp(previousState, action)
-- Dan Abaramov's Data Flow example in Redux
Cycle.jsinputs = read effects you care aboutoutputs = write effects you want performedall application logic is pure!
Logic is made easyImplicit data flow of your app becomes explicit.Immutable views of a mutable world
Debugging is made easyAll edge cases caught at compile-time.Single source of truth.Time Travel
Native (kotlin)Single-Atom-State (like redux)Pure Functional Reactive (like cycle)Composable (like cycle)
Aside: RxJavaReactive programming is programming with
asynchronous data streams
Andre Staltz
Global event emitter << Event buses << Data stream
Aside: RxJavaWhen we say streams, we mean push-based event streams,
not pull-based infinite list streams
Aside: RxJavaWhat can this look like in practice?
Streams of button tapsStreams of snapshots of changing dataStreams from network responses
1. View-Model Statedata class /*View-Model*/ State( val numLikes: Int, val numComments: Int, val showNewHighlight: Boolean, val imgUrl: String?, val showUndo: Boolean )
2. View Intentions// Mode is either tapped or untapped data class ViewIntentions( val photos: Observable<Photo>, val modes: Observable<Mode.Sum>, val globalReadTs: Observable<Long> )
3. Model State// Mode is either tapped or untapped data class /*Model*/ State( val photo: Photo?, val isNew: Boolean, val mode: Mode.Sum, ): RamState<...>
val initialState = State( photo = null, isNew = false, mode = Mode.untapped )
4. View Intentions => Model State ChangesState changes? We want functional code. We want
immutability.
Think of a state change as a function
func change(currentState: State) -> State /*nextState */
4. View Intentions => Model State Changesval model: (ViewIntentions) -> Observable<(State) -> State> = { intentions -> val modeChanges: Observable<(State) -> State> = intentions.modes.map{ /*...*/ }
val photoChanges: Observable<(State) -> State> = intentions.photos.map{ /*...*/ }
val tsChanges: Observable<(State) -> State> = intentions.globalReadTs.map{ /*...*/ }
Observable.merge( modeChanges, photoChanges, tsChanges) }
4. View Intentions => Model State Changesval modeChanges: Observable<(State) -> State> = intentions.modes.map{ mode -> { state: State -> State(state.photo, state.isNew, mode) } }
5. Model State => View-Model Stateval viewModel: (Observable<Model.State>)->Observable<ViewModel.State> = { stateStream -> stateStream .map{ state -> val undoable = state.mode == Mode.tapped val likes = state.photo?.like_details ?: emptyList() val comments = state.photo?.comments ?: emptyList() ViewModel.State( numLikes = likes.sumBy { it.multiplier }, numComments = comments.count, showNewHighlight = state.isNew, imgUrl = /* ... */, showUndo = /*...*/ ) } }
6. View-Model => Mutate the Viewclass PhotoComponent( viewIntentions: ViewIntentions, view: PhotoCellView ): StartStopComponent by Component( driver = /* ... */, model = /* ... */ )
6. View-Model => Mutate the Viewdriver = ViewDriver<ViewIntentions, ViewModel.State>( intention = viewIntentions, onViewState = { old, state -> if (old?.imgUrl != state.imgUrl) { view.setImg(state.imgUrl) } /* ... */ } ),
6. View-Model => Mutate the Viewmodel = ViewDriver.makeModel( initialState = Model.initialState, createState = Model.createState, model = Model.model, viewModel = ViewModel.viewModel )
ViewIntentionsThe inputs to your componentThe photo, the mode, the tap timestamp
ModelTransform the inputs into state changesChange mode, change isNew, change photo
ViewModelTransform model state to view-model stateExtract photo url, like counts, etc
ComponentApply mutations to your view based on your view-modelUse the View-Model to change the underlying Androidview
Under the hoodEnforce viewintentions/model/view-model structureRxJava does heavy-liftingand a magic scan
Implementation of Redux in one-linemodelStream.scan(initialState, { currentState, transform -> transform(currentState) })
BonusCycle.js-like side-effect driversConfigurable model state persistance within stateAuto-start and stop components onPause/onResume
Just like cycle
read effects are inputswrite effects are outputs
Effects are decoupled from business logic
Results: The GoodEasy to test (by hand + by unit test) & debugREALLY EASYMocking inputs is trivialUI component is defined ONLY by it's state
Results: The GoodEasy to maintainSpec change?
(possibly) add an input streamadd another map in the model
Results: The BadAnimations are hard
chase or interpolate underlying state?probably additive animations (google it)
Results: The Bad?Boiler plate
(screenshot of files)
Always the 4 piecescyklic repo has counter example in one file
Results: The ConclusionWe have more powerful tools now
(i.e. Kotlin + Functional programming)
Let's use them
Question everything
ThanksBrandon Kase Christina Lee
[email protected] [email protected]
@bkase_ @runchristinarun
bkase.com
Github: bkase/cyklic