andreaperizzato / Glue

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Glue

Introduction

Glue is a simple implementation of unidirectional data flow architecture in Swift.

It follows the idea of functional core - imperative shell, discussed by Gary Bernhardt in his great talk Boundaries.

Glue is very simple to understand and use as it defines clear patterns, in a very similar way to Redux.

Core principles

There are three key components:

  • Stores contain the State and expose a read-only view of it. The state can only be modified by dispatching Intents
  • Intents describe ways of updating the state, e.g. update the value of the counter. State mutations are performed by pure functions, called Reducers.
  • Effects describe side-effects such as a network request, database operation or pushing/popping the navigation stack. Effects are handled by EffectHandlers.

Stores, Reducers and Intents are all part of the so-called functional core where all the logic is implemented using pure functions which are very easy and fast to test. Side effects are by definition non-pure and they are part of the imperative shell.

Simple Example

Let's build a very simple application to increment and decrement a counter from 0 and that every time a multiple of 5 is reached, it plays a sound.

First of all we need to define the state:

struct AppState {
  var value = 0
}

Followed by all the possible mutations of the state:

enum AppIntent: Intent {
  case increment
  case decrement
}

The only side effect we have is playing a sound:

enum AppEffect: Effect {
  case playSound
}

Now we can implement the logic of the application:

func reducer(state: inout AppState, intent: Intent) -> [Effect] {
  switch intent {
  case .increment:
    state.value += 1
    if state.value % 5 == 0 {
      return [AppEffect.playSound]      
    }
    return []
  case .decrement where state.value > 0:
    state.value -= 1
    return []
  default:
    return []
  }
}

Not let's assume we are building an iOS app and want to play the sound. We can create an EffectHandler to handle the AppEffect.playSound effect:

class SoundEffectHandler: EffectHandler {
  
  private let player: AVAudioPlayer = ...

  func handle(effect: Effect, dispatch: @escaping DispatchFunc) {
    guard effect = AppEffect.playSound else { return }
    player.play()
  }
}

The last step is to setup the store and add all the effect handlers:

let store = Store(state: AppState(), reducer: reducer)
store.add(effectHandler: SoundEffectHandler())
return store

About


Languages

Language:Swift 98.4%Language:Objective-C 1.6%