• Stars
    star
    157
  • Rank 236,867 (Top 5 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Unidirectional reactive architecture

ReactiveFeedback

Unidirectional Reactive Architecture. This is a ReactiveSwift implemetation of RxFeedback

Documentation

Motivation

Requirements for iOS apps have become huge. Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base.

The goal of this library is to provide a simple and intuitive approach to designing reactive state machines.

Core Concepts

State

State is the single source of truth. It represents a state of your system and is usually a plain Swift type (which doesn't contain any ReactiveSwift primitives). Your state is immutable. The only way to transition from one State to another is to emit an Event.

struct Results<T: JSONSerializable> {
    let page: Int
    let totalResults: Int
    let totalPages: Int
    let results: [T]

    static func empty() -> Results<T> {
        return Results<T>(page: 0, totalResults: 0, totalPages: 0, results: [])
    }
}

struct Context {
    var batch: Results<Movie>
    var movies: [Movie]

    static var empty: Context {
        return Context(batch: Results.empty(), movies: [])
    }
}

enum State {
    case initial
    case paging(context: Context)
    case loadedPage(context: Context)
    case refreshing(context: Context)
    case refreshed(context: Context)
    case error(error: NSError, context: Context)
    case retry(context: Context)
}
Event

Represents all possible events that can happen in your system which can cause a transition to a new State.

enum Event {
    case startLoadingNextPage
    case response(Results<Movie>)
    case failed(NSError)
    case retry
}
Reducer

A Reducer is a pure function with a signature of (State, Event) -> State. While Event represents an action that results in a State change, it's actually not what causes the change. An Event is just that, a representation of the intention to transition from one state to another. What actually causes the State to change, the embodiment of the corresponding Event, is a Reducer. A Reducer is the only place where a State can be changed.

static func reduce(state: State, event: Event) -> State {
    switch event {
    case .startLoadingNextPage:
        return .paging(context: state.context)
    case .response(let batch):
        var copy = state.context
        copy.batch = batch
        copy.movies += batch.results
        return .loadedPage(context: copy)
    case .failed(let error):
        return .error(error: error, context: state.context)
    case .retry:
        return .retry(context: state.context)
    }
}
Feedback

While State represents where the system is at a given time, Event represents a trigger for state change, and a Reducer is the pure function that changes the state depending on current state and type of event received, there is not as of yet any type to emit events given a particular current state. That's the job of the Feedback. It's essentially a "processing engine", listening to changes in the current State and emitting the corresponding next events to take place. It's represented by a pure function with a signature of Signal<State, NoError> -> Signal<Event, NoError>. Feedbacks don't directly mutate states. Instead, they only emit events which then cause states to change in reducers.

public struct Feedback<State, Event> {
    public let events: (Scheduler, Signal<State, NoError>) -> Signal<Event, NoError>
}

func loadNextFeedback(for nearBottomSignal: Signal<Void, NoError>) -> Feedback<State, Event> {
    return Feedback(predicate: { !$0.paging }) { _ in
        return nearBottomSignal
            .map { Event.startLoadingNextPage }
        }
}

func pagingFeedback() -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.nextPage }) { (nextPage) -> SignalProducer<Event, NoError> in
        return URLSession.shared.fetchMovies(page: nextPage)
            .map(Event.response)
            .flatMapError { (error) -> SignalProducer<Event, NoError> in
                return SignalProducer(value: Event.failed(error))
            }
        }
}

func retryFeedback(for retrySignal: Signal<Void, NoError>) -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.lastError }) { _ -> Signal<Event, NoError> in
        return retrySignal.map { Event.retry }
    }
}

func retryPagingFeedback() -> Feedback<State, Event> {
    return Feedback<State, Event>(skippingRepeated: { $0.retryPage }) { (nextPage) -> SignalProducer<Event, NoError> in
        return URLSession.shared.fetchMovies(page: nextPage)
            .map(Event.response)
            .flatMapError { (error) -> SignalProducer<Event, NoError> in
                return SignalProducer(value: Event.failed(error))
            }
        }
}

The Flow

  1. As you can see from the diagram above we always start with an initial state.
  2. Every change to the State will be then delivered to all Feedback loops that were added to the system.
  3. Feedback then decides whether any action should be performed with a subset of the State (e.g calling API, observe UI events) by dispatching an Event, or ignoring it by returning SignalProducer.empty.
  4. Dispatched Event then goes to the Reducer which applies it and returns a new value of the State.
  5. And then cycle starts all over (see 2).
Example
let increment = Feedback<Int, Event> { _ in
    return self.plusButton.reactive
        .controlEvents(.touchUpInside)
        .map { _ in Event.increment }
}

let decrement = Feedback<Int, Event> { _ in
    return self.minusButton.reactive
        .controlEvents(.touchUpInside)
        .map { _ in Event.decrement }
}

let system = SignalProducer<Int, NoError>.system(initial: 0,
    reduce: { (count, event) -> Int in
        switch event {
        case .increment:
            return count + 1
        case .decrement:
            return count - 1
        }
    },
    feedbacks: [increment, decrement])

label.reactive.text <~ system.map(String.init)

Advantages

TODO

More Repositories

1

fastText_multilingual

Multilingual word vectors in 78 languages
Jupyter Notebook
1,189
star
2

DrawerKit

DrawerKit lets an UIViewController modally present another UIViewController in a manner similar to the way Apple's Maps app works.
Swift
780
star
3

ios-playbook

Ruby
396
star
4

orbit-mvi

An MVI framework for Kotlin and Android
Kotlin
385
star
5

Bento

Swift library for building component-based interfaces on top of UITableView and UICollectionView 🍱
Swift
371
star
6

iOS-Interview-Demo

Interview Demo Project for babylon health
Objective-C
200
star
7

certificate-transparency-android

Certificate transparency for Android and Java
Kotlin
197
star
8

rgat

A TensorFlow implementation of Relational Graph Attention Networks, paper: https://arxiv.org/abs/1904.05811
Python
112
star
9

hmrb

Python
70
star
10

android-playbook

Babylon Health Android Team Playbook
68
star
11

Stevenson

Stevenson is a Vapor framework designed to build integrations between Slack apps, GitHub, JIRA and CI services (CircleCI).
Swift
58
star
12

counterfactual-diagnosis

Python
52
star
13

Tota11y

Accessibility visualization toolkit for web content creators and editors.
JavaScript
44
star
14

fuzzymax

Code for the paper: Don't Settle for Average, Go for the Max: Fuzzy Sets and Max-Pooled Word Vectors, ICLR 2019.
Python
43
star
15

corrsim

Code for the papers: Correlation Coefficients and Semantic Textual Similarity, NAACL-HLT 2019 & Correlations between Word Vector Sets, EMNLP-IJCNLP 2019.
Python
35
star
16

lit-fhir

Opinionated library for easily constructuring FHIR (http://hl7.org/fhir) resources in Scala and Java.
Scala
35
star
17

Wall-E

A bot that monitors and manages your pull requests.
Swift
32
star
18

primock57

Dataset of 57 mock medical primary care consultations: audio, consultation notes, human utterance-level transcripts.
Python
31
star
19

github-proxy

A minimal caching proxy to GitHub's REST & GraphQL APIs
Python
29
star
20

neuralTPPs

Shell
27
star
21

medisim

Medical Similarity Dataset creation from SNOMED
Python
26
star
22

simba

Semantic similarity measures from Babylon Health
Python
16
star
23

decoding-decoders

Python
12
star
24

EHR-Rel

Biomedical concept relatedness benchmark sampled from electronic health records
9
star
25

multiverse

MultiVerse: Probabilistic Programming Language for Causal Reasoning
Python
9
star
26

MCSG

Python
8
star
27

TwinNetworks

A library for handling Structural Causal Models and performing interventional and counterfactual inference on them.
Python
8
star
28

web-interview

JavaScript
6
star
29

nameko-extras

Nameko run with autoloading, logging file CLI option
Python
6
star
30

event-stream-registry-ui

A React component for observing and monitoring event streams.
TypeScript
5
star
31

slack011y-bus

Python
4
star
32

sticky-layoutmanager

Java
2
star
33

fhir-hydrant

FHIR templating engine
Scala
2
star
34

snow-owl

🦉 Snow Owl - production ready, scalable terminology server (SNOMED CT, ICD-10, LOINC, dm+d, ATC and others)
Dockerfile
2
star
35

terraform-provider-aws-babylon

Go
1
star
36

laymaker

1
star