Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features. To learn more about Unidirectional Flow in Swift, take a look at my dedicated post.
struct SearchState: Equatable {
var repos: [Repo] = []
var isLoading = false
}
enum SearchAction: Equatable {
case search(query: String)
case setResults(repos: [Repo])
}
struct SearchReducer: Reducer {
func reduce(oldState: SearchState, with action: SearchAction) -> SearchState {
var state = oldState
switch action {
case .search:
state.isLoading = true
case let .setResults(repos):
state.repos = repos
state.isLoading = false
}
return state
}
}
actor SearchMiddleware: Middleware {
struct Dependencies {
var search: (String) async throws -> SearchResponse
static var production: Dependencies {
.init { query in
guard var urlComponents = URLComponents(string: "https://api.github.com/search/repositories") else {
return .init(items: [])
}
urlComponents.queryItems = [.init(name: "q", value: query)]
guard let url = urlComponents.url else {
return .init(items: [])
}
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(SearchResponse.self, from: data)
}
}
}
let dependencies: Dependencies
init(dependencies: Dependencies) {
self.dependencies = dependencies
}
func process(state: SearchState, with action: SearchAction) async -> SearchAction? {
switch action {
case let .search(query):
let results = try? await dependencies.search(query)
guard !Task.isCancelled else {
return .setResults(repos: state.repos)
}
return .setResults(repos: results?.items ?? [])
default:
return nil
}
}
}
typealias SearchStore = Store<SearchState, SearchAction>
@MainActor struct SearchContainerView: View {
@State private var store = SearchStore(
initialState: .init(),
reducer: SearchReducer(),
middlewares: [SearchMiddleware(dependencies: .production)]
)
@State private var query = ""
var body: some View {
List(store.repos) { repo in
VStack(alignment: .leading) {
Text(verbatim: repo.name)
.font(.headline)
if let description = repo.description {
Text(verbatim: description)
}
}
}
.redacted(reason: store.isLoading ? .placeholder : [])
.searchable(text: $query)
.task(id: query) {
await store.send(.search(query: query))
}
.navigationTitle("Github Search")
}
}
Add this Swift package in Xcode using its Github repository url. (File > Swift Packages > Add Package Dependency...)
v0.3.x - Introduced the new Observation framework available only on iOS 17 and macOS 14. v0.2.x - Use previous versions to target older versions of iOS and macOS.
Majid Jabrayilov: cmecid@gmail.com
swift-unidirectional-flow package is available under the MIT license. See the LICENSE file for more info.