This talk was given at Realm on 3.31.16 and makes the case for looking to the web to find better mobile strategies. Also covers one example of a way you can take a web framework (Cycle.js) and port it to native.
Reduxing UI: Borrowing the Best of Web to Make Android Better
1. Borrowing the best of the web
to make native better
Brandon Kase & Christina Lee
2. Why are we here?
Let me tell you a story!
about a button
a complicated button
3. Take 1:
boss asks for friend button
we say, 'OK! We'll do it this week!'
we do not finish it in a week
woah...this is very complicated!
boss is less than thrilled
4. Take 2:
boss asks for friend button
we say, 'OK! We'll do it this week!'
we modify our approach
we do not finish it in a week
we finish it in a day!
boss is thrilled
Brandon and Christina still have jobs, hurray!
5. What went wrong
Fetch data before view transitions
Optimistically update components
Send server requests and react to responses
8. As developers, we are expected to handle
optimistic updates, server- side rendering,
fetching data before performing route
transitions, and so on... This complexity is
difficult to handle as we’re mixing two
concepts that are very hard for the human
mind to reason about: mutation and
asynchronicity. I call them Mentos and Coke”
Motivation | Redux
17. 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
24. Logic is made easy
Implicit data flow of your app becomes explicit.
Immutable views of a mutable world
Debugging is made easy
All edge cases caught at compile-time.
Single source of truth.
Time Travel
25. How can you adopt this
View:
React native
Anvil
State:
Direct port of Redux/Cycle/etc.
26. We focused on State
Don't have to fight Android's UI framework
Easy to introduce
41. 1. View-Model State
data class /*View-Model*/ State(
val numLikes: Int,
val numComments: Int,
val showNewHighlight: Boolean,
val imgUrl: String?,
val showUndo: Boolean
)
42.
43. 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>
)
44.
45. 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
)
46. 4. View Intentions => Model State Changes
State changes? We want functional code. We want
immutability.
Think of a state change as a function
func change(currentState: State) -> State /*
nextState */
47. 4. View Intentions => Model State Changes
val 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)
}
48. 4. View Intentions => Model State Changes
val modeChanges: Observable<(State) -> State> =
intentions.modes.map{ mode ->
{ state: State -> State(state.photo, state.isNew, mode) }
}
49.
50. 5. Model State => View-Model State
val 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 = /*...*/
)
}
}
51.
52. 6. View-Model => Mutate the View
class PhotoComponent(
viewIntentions: ViewIntentions,
view: PhotoCellView
): StartStopComponent by Component(
driver = /* ... */,
model = /* ... */
)
54. 6. View-Model => Mutate the View
model = ViewDriver.makeModel(
initialState = Model.initialState,
createState = Model.createState,
model = Model.model,
viewModel = ViewModel.viewModel
)
55. Stick it in a recycler-view, hook up the side-effects into view
intentions and you're done
56.
57. ViewIntentions
The inputs to your component
The photo, the mode, the tap timestamp
Model
Transform the inputs into state changes
Change mode, change isNew, change photo
ViewModel
Transform model state to view-model state
Extract photo url, like counts, etc
Component
Apply mutations to your view based on your view-model
Use the View-Model to change the underlying Android
view
58. Under the hood
Enforce viewintentions/model/view-model structure
RxJava does heavy-lifting
and a magic scan