Swiftpack.co - afiorito/Reflow as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by afiorito.
afiorito/Reflow v3.0.0
A Redux-like implementation of the unidirectional data flow architecture in Swift using Combine.
⭐️ 2
🕓 44 weeks ago
iOS macOS watchOS
.package(url: "https://github.com/afiorito/Reflow.git", from: "v3.0.0")

icon Reflow

Reflow is a Redux-like implementation of the unidirectional data flow architecture in Swift using Combine.

About Reflow

Reflow relies on a few concepts:

  • A Store contains the entire state of your app in a single data structure. The state is modified by dispatching actions to the store. Whenever the state changes, all selectors are updated via a Combine publisher.

  • A Reducer is a pure function that when given action, uses the current state to create a new state.

  • An Action is a way of describing a change in state. Actions don't have logic, they are dispatched to the store and processed by reducers.

Reflow Lifecycle

Installation

Add Reflow to your project using Swift Package Manager. In your Xcode project, select File > Swift Packages > Add Package Dependency and enter the repository URL.

Documentation

Basic Usage

1. Create a state representation.

struct CounterState {
  var count: Int
  var name: String
}

2. Create a dispatchable action.

enum CounterAction: Action {
  case increment
  case loadedName(String)
}

3. Create a reducer.

struct CounterState: Equatable {
  ...

  static func reducer(state: Self, action: Action) -> Self {
    switch action {
      case CounterAction.increment:
        return Self(count: state.count + 1, name: state.name)
      case CounterAction.loadedName(let name):
        return Self(count: state.count, name: name)
      default:
          return state
    }
  }
}

4. Create a store.

let store = Store(reducer: CounterState.reducer, initialState: CounterState(count: 0, name: ""))

5. Dispatch an action.

store.dispatch(CounterAction.increment)

6. Observe the store.

The store conforms to Observable. Store state can automatically be observed by SwiftUI views.

struct ContentView: View {
  @Environment(Store<CounterState>.self) var store

  var body: some View {
      Text("Counter: \(store.state.counter)")
      .onTapGesture {
          store.dispatch(CounterAction.increment)
      }
  }
}

Effects

Normal actions don't allow for side effects like making a network call or accessing the disk. Effects allow you to perform asynchronous operations with side effects. Effects are not passed through the middleware pipeline and never reach the reducer but can dispatch other actions and even effects.

A synchronous effect can access state and dispatch other actions.

let store = Store(reducer: CounterState.reducer, initialState: CounterState(count: 0, name: ""))

let effect = Effect<CounterState> { dispatch, getState in
    dispatch(CounterAction.increment)
}

store.dispatch(effect)

An asynchronous effect can access state and dispatch other actions.

func getName() async throws -> String {
    let url = URL(string: "https://api.counter.com/name")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}

let store = Store(reducer: CounterState.reducer, initialState: CounterState(count: 0, name: ""))

let effect = AsyncEffect<CounterState> { dispatch, _ in
    let name = try? await getName()
    await MainActor.run {
        dispatch(CounterAction.loadedName(name ?? ""))
    }
}

store.dispatch(effect)

Middleware

Middleware transform actions and can create more actions, while having access to the current state at any point. These actions are passed from middleware to middleware until they reach the reducer.

Middleware are functions that can be registered on store creation:

let logger: Middleware<CounterState> = { dispatch, getState in
  return { next in
    return { action in
      print("Received action: \(action), with current state: \(getState())")
      next(action)  // pass the action to the next middleware
    }
  }
}

// register the middleware
let store = Store(reducer: CounterState.reducer, initialState: CounterState(count: 0, name: ""), middleware: [logger])

Combine Reducers

As your application state becomes more complex and you create more actions, it may be useful to split your state into multiple reducers. Each reducer handles a single key of your state.

struct AuthState {
  var token: String

  static func reducer(state: Self, action: Action) -> Self {
    return state
  }
}

struct AppState {
  var counter = CounterState(count: 0, name: "")
  var auth = AuthState(token: "")
}

let reducers: Reducer<AppState> = combineReducers(
  withKey(\.counter, use: CounterState.reducer),
  withKey(\.auth, use: AuthState.reducer)
)

// pass the combined reducers to the store
let store = Store(reducer: reducers, initialState: AppState())

License

Reflow is available under the MIT license. See the LICENSE file for more info.

GitHub

link
Stars: 2
Last commit: 44 weeks ago
Advertisement: IndiePitcher.com - Cold Email Software for Startups

Related Packages

Release Notes

Reflow v3.0.0
44 weeks ago

This release contains breaking changing and is not backwards compatible with previous version.

The store now uses @Observable instead of combine to subscribe to store changes. Reflow now requires a minimum of iOS 17.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics