Unidirectional data flow is the same idea React 7,897 made popular, and each piece has a counterpart:
| Concept | React (LAMP Stack Development, MySQL) | Android |
|---|---|---|
| State container | useReducer, Redux 3,824 store, Zustand 58,763 | ViewModel |
| Current state | state from the hook or useSelector | StateFlow, collectAsStateWithLifecycle() |
| Actions | dispatch({ type: "toggle", book }) | onEvent(CartEvent.Toggle(book)) |
| Reducer | pure (state, action) => state | when (event) producing a new immutable state |
| Side effects | thunks, sagas, useEffect | viewModelScope.launch, Channels |
| Lifetime | component or app | back-stack entry or activity, survives rotation |
The sealed CartEvent interface is a Redux action type that the compiler checks: a when over it must handle every case (Exhaustive when Expressions), where a JavaScript reducer's switch silently ignores a typo. Immutable state plays the same role in both: copy() and ids + id build new values the way the spread operator does, so equality checks tell the UI what changed.
The differences: there is no global store by default, so each screen's ViewModel is closer to one useReducer per page, and shared state lives in a ViewModel scoped wider (like BookNest's cart) or in the data layer. Async work needs no middleware, just a coroutine. And a ViewModel outlives rotation, and SavedStateHandle outlives the process, which no React state does without explicit persistence. Orbit MVI 1,345 (github.com/orbit-mvi/orbit-mvi (https://github.com/orbit-mvi/orbit-mvi 1,345 ), Apache 2.0) adds Redux-style discipline on top.