A type-safe state management library for SwiftUI applications. Flow provides a unidirectional data flow architecture and supports Observation and Swift 6 Concurrency.
import Flow
import SwiftUI
struct CounterFeature: Feature {
@Observable
final class State {
var count = 0
}
enum Action: Sendable {
case increment
case decrement
}
func handle() -> ActionHandler<Action, State, Void> {
ActionHandler { action, state in
switch action {
case .increment:
state.count += 1
return .none
case .decrement:
state.count -= 1
return .none
}
}
}
}
struct CounterView: View {
@State private var store = Store(
initialState: .init(),
feature: CounterFeature()
)
var body: some View {
VStack(spacing: 20) {
Text("\(store.state.count)")
.font(.largeTitle)
HStack(spacing: 16) {
Button("-") {
store.send(.decrement)
}
Button("+") {
store.send(.increment)
}
}
}
}
}All state changes flow in one direction: Action → Handler → State → View. This makes your app's behavior predictable and easy to debug.
Button("Load") {
store.send(.load) // Action flows to handler
}
// Handler updates state
case .load:
return .run { state in
state.data = try await api.fetch() // State flows to view
}- Predictable data flow
- Easy to trace state changes
- Well-defined inputs and outputs
Each view holds its own state with @State, aligned with SwiftUI's philosophy. No global store, no store hierarchies.
struct CounterView: View {
@State private var store = Store(
initialState: .init(),
feature: CounterFeature()
)
var body: some View {
VStack {
Text("\(store.state.count)")
Button("Increment") {
store.send(.increment)
}
}
}
}- Clear lifecycle (store lives with view)
- No global state management
- Memory efficient
Actions return typed results, enabling functional patterns and making side effects explicit.
enum ActionResult: Sendable {
case saved(id: String)
}
// In handler
case .save(let title):
return .run { state in
let todo = try await api.create(title: title)
return .saved(id: todo.id) // Return result
}
// In view
Button("Save") {
Task {
let result = await store.send(.save(title: title)).value
if case .success(.saved(let id)) = result {
await navigator.navigate(to: .detail(id: id))
}
}
}- Actions return values like functions
- Parent controls navigation and side effects
- Type-safe contracts
Directly update state inside async operations—safely. Flow leverages Swift 6's defaultIsolation(MainActor.self) for compile-time safety.
case .fetchUser:
state.isLoading = true
return .run { state in
// Directly update state in async context!
let user = try await api.fetchUser()
state.user = user
state.isLoading = false
}- Code locality (loading, fetching, errors in one place)
- Intuitive (write naturally like regular Swift)
- Compile-time safety (data races caught at compile time)
Uses SwiftUI's standard @Observable instead of @ObservableObject or @Published.
@Observable
final class State {
var count = 0 // No @Published needed
}- No Combine dependency
- Less boilerplate
- Platform aligned with SwiftUI
Add Flow to your Package.swift:
dependencies: [
.package(url: "https://github.com/ViewFeature/Flow.git", from: "1.3.1")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "Flow", package: "Flow")
],
swiftSettings: [
.defaultIsolation(MainActor.self) // Recommended
]
)
]- Select File → Add Package Dependencies
- Enter the URL:
https://github.com/ViewFeature/Flow.git - Select version:
1.3.1or later
Recommended: Add -default-isolation MainActor to your target's Build Settings → Other Swift Flags.
- iOS 18.0+ / macOS 15.0+ / watchOS 11.0+ / tvOS 18.0+
- Swift 6.2+
- Xcode 16.2+
Contributions are welcome!
Before submitting a pull request, please review the Contributing Guide. If you have questions or ideas, start a Discussion.
- 🐛 Report Issues - Bug reports and feature requests
- 💬 Discussions - Share questions and ideas
Flow is inspired by the following libraries and communities:
- Redux - Unidirectional data flow architecture
- ReSwift - Unidirectional data flow in Swift
- The Composable Architecture - State management patterns in Swift
Flow is distributed under the MIT License. See the LICENSE file for details.