https://github.com/vadimtrifonov/RxStateReducer
Asynchronous state management with RxSwift
https://github.com/vadimtrifonov/RxStateReducer
Last synced: 2 days ago
JSON representation
Asynchronous state management with RxSwift
- Host: GitHub
- URL: https://github.com/vadimtrifonov/RxStateReducer
- Owner: vadimtrifonov
- License: mit
- Created: 2019-02-17T21:23:18.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2019-02-23T00:53:14.000Z (about 6 years ago)
- Last Synced: 2024-11-14T08:35:30.696Z (6 months ago)
- Language: Swift
- Homepage:
- Size: 69.3 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-rxswift - RxStateReducer
README
# Rx State Reducer
This repository demonstrates a pattern of asynchronous state management based on ideas of reactive programming, state reducer and unidirectional data flow. This is not a library, because you don’t need yet another library to implement this.
## Running
1) Install [Carthage](https://github.com/Carthage/Carthage)
```
brew install carthage
```2) Build the dependencies
```
carthage bootstrap --platform iOS
```## Problem
**Asynchronous state management is a non-trivial modelling exercise and requires a reliable approach to keep it concise and maintainable.**
Most trivial applications start without any explicit state management approach. However, things quickly get out of hand when the number of states in which a system can reside starts to grow.
[Finite-State Machine](https://en.wikipedia.org/wiki/Finite-state_machine) and [State Pattern](https://en.wikipedia.org/wiki/State_pattern) can help manage synchronous state transitions, but are not designed to handle asynchronous behaviour.
> Unless you can model your entire system synchronously, a single asynchronous source breaks imperative programming.
>
> -- Jake Wharton## Solution
**Explicitly define the state of a system, then use a reducer function to compute a new state based on the previous state. Use reactive streams to handle asynchronous tasks and make data flow in one direction.**
1. An event is received and transformed to an `Action`.
2. The `Action` is handled and transformed to `Mutation`. _This transformation is needed when `Actions` require asynchronous handling. Such `Actions` produce multiple `Mutations` (e.g., (1) `loading`, (2) `loaded`)_
3. Each `Mutation` is passed to the `Reducer` function, which uses it to transform the current `State` to a new `State`.Where:
- `Action` - an interpretation of the system or user event
- `Mutation` - a result of the `Action` handling
- `State` - a model of the system or use case
- `Reducer` - a pure function which takes a `State` and a `Mutation` and produces a new `State`
## Example
1. `View` receives a user event.
2. `View` transforms the event to an `Action`.
3. `View` propagates the `Action` to `Interactor`.
4. `Interactor` handles the `Action` and transforms it to `Mutations`.
5. `Interactor` uses each `Mutation` to transform the current `State` to a new `State`.
6. `Interactor` propagates the new `State` to `View`
7. `View` updates itself using the new `State`. The unidirectional feedback loop is now complete.Given that `actions` is `Observable`, `states` is `Observable`, the unidirectional feedback loop is expressed as:
```
states = actions.flatMap(handleAction).scan(State.initial, accumulator: reduce)
````Action` and `Mutation` are expressed as an `enum`:
```
enum Action {
case reload
}enum Mutation {
case loading
case records([Record])
case failure(Error)
}
````State` is expressed as a `struct` (if there is a strict number of finite states, then `enum`):
```
struct State {
var records: [Record]
var isLoading: Bool
var error: Error?static var initial = State(records: [], isLoading: false, error: nil)
}
````Reducer` is a pure function:
```
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case let records(records):
state.records = records
case let failure(error):
...
case loading:
...
}
return state
}
```The function `handleAction` is asynchronous:
```
func handleAction(_ action: Action) -> Observable {
switch action {
case reload:
return gateway.fetchRecords()
.map(Mutation.records)
.startWith(Mutation.loading)
.catchError({ .just(Mutation.failure($0)) })
}
}
```## State introspection
Check branch [state-feedback](https://github.com/vadimtrifonov/RxStateReducer/tree/state-feedback)To introspect `State` during `handleAction` it can be fed back into the loop:
```
func start(actions: Observable) -> Observable {
let states = BehaviorRelay(value: State.initial)
return actions
.withLatestFrom(states, resultSelector: { ($0, $1) })
.flatMap(handleAction)
.scan(states.value, accumulator: reduce)
.do(onNext: states.accept)
}
```The function `handleAction` will now take two parameters:
```
func handleAction(_ action: Action, state: State) -> Observable {
...
}
```## Further reading
* [Managing State with RxJava](https://www.youtube.com/watch?v=0IKHxjkgop4)
* [Reactive Apps with Model-View-Intent - Part3 - State Reducer](http://hannesdorfmann.com/android/mosby3-mvi-3)