Skip to content

Commit 4418d95

Browse files
authored
Merge pull request #17 from sideeffect-io/feature/improve-search-apis
samples: improve SearchApis
2 parents c993963 + 9c101fd commit 4418d95

File tree

9 files changed

+69
-29
lines changed

9 files changed

+69
-29
lines changed

Samples/SearchApis/SearchApis/Feature/SideEffect/Search.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
// Created by Thibault Wittemberg on 21/08/2022.
66
//
77

8-
@Sendable func search(query: String, searchFunction: (String) async throws -> [Entry]) async -> Event {
8+
@Sendable func search(
9+
query: String,
10+
searchFunction: (String) async throws -> [Entry]
11+
) async -> Event? {
912
do {
1013
let entries = try await searchFunction(query)
1114
return Event.searchHasSucceeded(entries: entries)
12-
} catch {
15+
} catch is CancellationError {
1316
return Event.searchHasFailed
17+
} catch {
18+
logger.debug("Search with query \(query) was cancelled")
19+
return nil
1420
}
1521
}

Samples/SearchApis/SearchApis/Feature/StateMachine/Event.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import AsyncStateMachine
99

1010
enum Event: DSLCompatible, Equatable {
11-
case searchIsRequested(query: String)
12-
case searchHasSucceeded(entries: [Entry])
13-
case searchHasFailed
11+
case searchIsRequested(query: String)
12+
case searchHasSucceeded(entries: [Entry])
13+
case searchHasFailed
14+
case refreshIsRequested
1415
}

Samples/SearchApis/SearchApis/Feature/StateMachine/Output.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
import AsyncStateMachine
99

1010
enum Output: DSLCompatible, Equatable {
11-
case search(query: String)
11+
case search(query: String)
1212
}

Samples/SearchApis/SearchApis/Feature/StateMachine/State.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,23 @@
88
import AsyncStateMachine
99

1010
enum State: DSLCompatible, Equatable {
11-
case idle
12-
case searching(query: String)
13-
case loaded(query: String, entries: [Entry])
14-
case failed
11+
case idle
12+
case searching(context: Context)
13+
case loaded(context: Context)
14+
case failed
15+
16+
struct Context: Equatable {
17+
let query: String
18+
var entries: [Entry] = []
19+
}
1520
}
1621

1722
extension State: CustomStringConvertible {
1823
var description: String {
1924
switch self {
2025
case .idle: return "idle"
21-
case .searching(let query): return "search with the query \(query)"
22-
case .loaded(_, let entries): return "loaded with \(entries.count) entries"
26+
case .searching(let context): return "search with the query \(context.query), previous \(context.entries.count) entries"
27+
case .loaded(let context): return "loaded with the query \(context.query), previous \(context.entries.count) entries"
2328
case .failed: return "failed"
2429
}
2530
}

Samples/SearchApis/SearchApis/Feature/StateMachine/StateMachine.swift

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,42 @@ func stateMachine(initial: State) -> StateMachine<State, Event, Output> {
1111
StateMachine(initial: .idle) {
1212
When(states: OneOf{
1313
State.idle
14-
State.loaded(query:entries:)
14+
State.loaded(context:)
1515
State.failed
1616
}) { _ in
1717
Execute.noOutput
1818
} transitions: { _ in
1919
On(event: Event.searchIsRequested(query:)) { query in
2020
Guard(predicate: !query.isEmpty)
2121
} transition: { query in
22-
Transition(to: State.searching(query: query))
22+
Transition(to: State.searching(context: State.Context(query: query)))
2323
}
2424
}
2525

26-
When(state: State.searching(query:)) { query in
27-
Execute(output: Output.search(query: query))
28-
} transitions: { query in
26+
When(state: State.searching(context:)) { context in
27+
Execute(output: Output.search(query: context.query))
28+
} transitions: { context in
2929
On(event: Event.searchHasSucceeded(entries:)) { entries in
30-
Transition(to: State.loaded(query: query, entries: entries))
30+
Transition(to: State.loaded(context: State.Context(query: context.query, entries: entries)))
3131
}
3232

3333
On(event: Event.searchHasFailed) { _ in
3434
Transition(to: State.failed)
3535
}
36+
37+
On(event: Event.searchIsRequested(query:)) { query in
38+
Guard(predicate: !query.isEmpty)
39+
} transition: { query in
40+
Transition(to: State.searching(context: State.Context(query: query, entries: context.entries)))
41+
}
42+
}
43+
44+
When(state: State.loaded(context:)) { _ in
45+
Execute.noOutput
46+
} transitions: { context in
47+
On(event: Event.refreshIsRequested) { _ in
48+
Transition(to: State.searching(context: context))
49+
}
3650
}
3751
}
3852
}

Samples/SearchApis/SearchApis/Feature/View/RootView.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ struct RootView: View {
1717
List(self.stateMachine.state.entries) { entry in
1818
Text("\(entry.api) (\(entry.link))")
1919
}
20+
.refreshable {
21+
await self.stateMachine.send(
22+
Event.refreshIsRequested,
23+
resumeWhen: State.loaded(context:)
24+
)
25+
}
2026

2127
if self.stateMachine.state.isSearching {
2228
ProgressView()
@@ -59,10 +65,18 @@ struct RootView_Previews: PreviewProvider {
5965

6066
static var previews: some View {
6167
Group {
62-
RootView(stateMachine: mockViewStateMachine(initial: .idle))
63-
RootView(stateMachine: mockViewStateMachine(initial: .searching(query: "mar")))
64-
RootView(stateMachine: mockViewStateMachine(initial: .loaded(query: "mar", entries: mockEntries)))
65-
RootView(stateMachine: mockViewStateMachine(initial: .failed))
68+
RootView(
69+
stateMachine: mockViewStateMachine(initial: .idle)
70+
)
71+
RootView(
72+
stateMachine: mockViewStateMachine(initial: .searching(context: State.Context(query: "Mar")))
73+
)
74+
RootView(
75+
stateMachine: mockViewStateMachine(initial: .loaded(context: State.Context(query: "mar", entries: mockEntries)))
76+
)
77+
RootView(
78+
stateMachine: mockViewStateMachine(initial: .failed)
79+
)
6680
}
6781
}
6882
}

Samples/SearchApis/SearchApis/Feature/View/State+ViewState.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import SwiftUI
1010
extension State {
1111
var query: String {
1212
switch self {
13-
case .searching(let query), .loaded(let query, _): return query
13+
case .searching(let context), .loaded(let context): return context.query
1414
default: return ""
1515
}
1616
}
1717

1818
var entries: [Entry] {
1919
switch self {
20-
case .loaded(_, let entries): return entries
20+
case .searching(let context), .loaded(let context): return context.entries
2121
default: return []
2222
}
2323
}
2424

2525
var isSearching: Bool {
26-
if case .searching = self {
26+
if case .searching(let context) = self, context.entries.isEmpty {
2727
return true
2828
}
2929
return false

Samples/SearchApis/SearchApis/Implementation/SearchFromRestApi.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import Foundation
1111
guard let url = URL(string: "https://api.publicapis.org/entries?title=\(query)") else {
1212
throw NSError(domain: "network", code: 1)
1313
}
14-
14+
1515
let (data, response) = try await URLSession.shared.data(from: url)
16-
16+
1717
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
1818
throw NSError(domain: "network", code: 2)
1919
}
20-
20+
2121
let result = try JSONDecoder().decode(Response.self, from: data)
2222
return result.entries
2323
}

Samples/SearchApis/SearchApis/SearchApisApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var viewStateMachine: RawViewStateMachine<State, Event, Output> {
1313

1414
let stateMachine = stateMachine(initial: .idle)
1515
let runtime = Runtime<State, Event, Output>()
16-
.map(output: Output.search(query:), to: searchSideEffect)
16+
.map(output: Output.search(query:), to: searchSideEffect, priority: .low, strategy: .cancel(when: State.searching(context:)))
1717
.register(middleware: logToOSLog(state:))
1818

1919
let asyncStateMachine = AsyncStateMachine(stateMachine: stateMachine, runtime: runtime)

0 commit comments

Comments
 (0)