Skip to content

Commit

Permalink
Concurrency Beta (#1189)
Browse files Browse the repository at this point in the history
* more main actor audit

* wip

* wip

* fix

* better task result ==

* task result tests

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix merge conflicts

* wip

* wip

* lots of doc fixes and modernizations

* lots more docs and better hashable conformance for TaskResult

* more docs

* clean up

* more tests and docs

* clean up

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* small clean up

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* explicit

* wip

* fix bug in TestStore.receive

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fixes

* wip

* tools for non-deterministic TestStore.receive

* fix

* wip

* wip

* remove inAnyOrder stuff

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* convert download case study to use async/await

* animations

* fix tests

* remove executor experiment

* wip

* wip

* wip

* wip

* wip

* speech simplification

* wip

* wip

* wip

* wip

* wip

* wip

* add a few todos

* wrote some tests

* simplify speech recognizer

* fix tests

* update some docs about error throwing behavior

* wip

* wip

* fix

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Swift 5.5.2 fixes

* wip

* Bump timeout

* wip

* wip

* Finesse

* proper way to detect main queue

* extra guard

* revert main queue check

* move stuff around

* docs

* fixed a bunch of warnings

* Fix references

* clean up

* clean up

* fix a bunch of warnings

* clean up

* un-soft deprecate concatenate

* async teststore.send

* fix uikit tests

* drop sendable

* wip

* wip

* wip

* wip

* wip

* clean up

* clean up

* reorganize, remove extra task cancellation handler

* wip

* wip

* wip

* wip

* wip

* wip

* Make TestStore.send async (#1190)

* async teststore.send

* fix uikit tests

* Converted all tests to async

* clean up

* added docs

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <[email protected]>

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <[email protected]>

* docs and readme update

* Update README.md

* Update Tests/ComposableArchitectureTests/StoreTests.swift

Co-authored-by: Stephen Celis <[email protected]>

* fix

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <[email protected]>

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <[email protected]>

* Update Sources/ComposableArchitecture/TestStore.swift

Co-authored-by: Stephen Celis <[email protected]>

* clean up

Co-authored-by: Stephen Celis <[email protected]>

* wip

* wip

* wip

* make fetchNumber throwing and fix tests

* effect basics clean up

* use local state for isLoading in refreshable case study

* clean up

* fix test

* wip

* wip

* wip

* wip

* wip

* wip

* fixes

* clean up

* clean up

* Simplify

* wip

* clean up

* wip

* AsyncStream.finished()

* give Send a public initializer

* make send public

* temporarily make box public

* remove concurrency flag

* wip

* wip

* wip

* wip

* wip

* docs

* speech

* simplify

* clean up;

* unchecked sendable

* clean up

* clean up

* wip

* docs

* docs

* more docs

* lots of docs

* wip

* wip

* wip

* more docs for streamWithContinuation

* wip

* wip

* wip

* Make internal, too

* wip

* Remove sendability detection

It breaks things, like:

    let request = UncheckedSendable(
      SKProductsRequest(productIdentifiers: []
    )
    // UncheckedSendable<NSObject> // *not* _<SKProductsRequest>

* wip

* doc clean up;

* fixed some todos

* docs

* wip

* remove thread safety FAQ from readme

* fix test

* wip

* docs clean up

* docs clean up

* added a testing article and fixed some docs

* rearrange

* docs clean up

* wip

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md

Co-authored-by: Thomas Grapperon <[email protected]>

* wip

* wip

* wip

Co-authored-by: Stephen Celis <[email protected]>
Co-authored-by: Thomas Grapperon <[email protected]>
(cherry picked from commit 108e3a536fcebb16c4f247ef92c2d7326baf9fe3)

# Conflicts:
#	Examples/CaseStudies/SwiftUICaseStudies/00-Core.swift
#	Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Animations.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Basics.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Cancellation.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-LongLiving.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Refreshable.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-SystemEnvironment.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Timers.swift
#	Examples/CaseStudies/SwiftUICaseStudies/02-Effects-WebSocket.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Lists-LoadThenNavigate.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Lists-NavigateAndLoad.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-LoadThenNavigate.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-NavigateAndLoad.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Sheet-LoadThenPresent.swift
#	Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Sheet-PresentAndLoad.swift
#	Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ElmLikeSubscriptions.swift
#	Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-Lifecycle.swift
#	Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads/DownloadClient.swift
#	Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift
#	Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ReusableFavoriting.swift
#	Examples/CaseStudies/SwiftUICaseStudies/FactClient.swift
#	Examples/CaseStudies/SwiftUICaseStudies/Internal/ResignFirstResponder.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-AnimationsTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-BasicsTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-CancellationTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-RefreshableTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-TimersTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-WebSocketTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-LifecycleTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-ReusableFavoritingTests.swift
#	Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-ReusableOfflineDownloadsTests.swift
#	Examples/CaseStudies/UIKitCaseStudies/LoadThenNavigate.swift
#	Examples/CaseStudies/UIKitCaseStudies/NavigateAndLoad.swift
#	Examples/Search/Search/SearchView.swift
#	Examples/Search/Search/WeatherClient.swift
#	Examples/SpeechRecognition/SpeechRecognition/SpeechClient/Live.swift
#	Examples/SpeechRecognition/SpeechRecognition/SpeechRecognition.swift
#	Examples/SpeechRecognition/SpeechRecognition/SpeechRecognitionApp.swift
#	Examples/SpeechRecognition/SpeechRecognitionTests/SpeechRecognitionTests.swift
#	Examples/TicTacToe/App/RootView.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/AppCore/AppCore.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClientLive/LiveAuthenticationClient.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift
#	Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift
#	Examples/TicTacToe/tic-tac-toe/Tests/AppCoreTests/AppCoreTests.swift
#	Examples/TicTacToe/tic-tac-toe/Tests/LoginCoreTests/LoginCoreTests.swift
#	Examples/TicTacToe/tic-tac-toe/Tests/LoginSwiftUITests/LoginSwiftUITests.swift
#	Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorCoreTests/TwoFactorCoreTests.swift
#	Examples/TicTacToe/tic-tac-toe/Tests/TwoFactorSwiftUITests/TwoFactorSwiftUITests.swift
#	Examples/Todos/Todos/Todos.swift
#	Examples/Todos/Todos/TodosApp.swift
#	Examples/Todos/TodosTests/TodosTests.swift
#	Examples/VoiceMemos/VoiceMemos/AudioRecorderClient/LiveAudioRecorderClient.swift
#	Examples/VoiceMemos/VoiceMemos/VoiceMemo.swift
#	Examples/VoiceMemos/VoiceMemos/VoiceMemos.swift
#	Examples/VoiceMemos/VoiceMemos/VoiceMemosApp.swift
#	Examples/VoiceMemos/VoiceMemosTests/VoiceMemosTests.swift
#	Package.swift
#	README.md
#	Sources/ComposableArchitecture/Documentation.docc/Articles/GettingStarted.md
#	Sources/ComposableArchitecture/Effect.swift
#	Sources/ComposableArchitecture/Effects/Animation.swift
#	Sources/ComposableArchitecture/Effects/Cancellation.swift
#	Sources/ComposableArchitecture/Effects/Concurrency.swift
#	Sources/ComposableArchitecture/Effects/Timer.swift
#	Sources/ComposableArchitecture/Internal/Create.swift
#	Sources/ComposableArchitecture/Internal/Deprecations.swift
#	Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift
#	Sources/ComposableArchitecture/Reducer.swift
#	Sources/ComposableArchitecture/Store.swift
#	Sources/ComposableArchitecture/SwiftUI/Alert.swift
#	Sources/ComposableArchitecture/SwiftUI/Binding.swift
#	Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift
#	Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift
#	Sources/ComposableArchitecture/SwiftUI/SwitchStore.swift
#	Sources/ComposableArchitecture/TestSupport/TestStore.swift
#	Sources/ComposableArchitecture/ViewStore.swift
#	Tests/ComposableArchitectureTests/ComposableArchitectureTests.swift
#	Tests/ComposableArchitectureTests/EffectDebounceTests.swift
#	Tests/ComposableArchitectureTests/EffectTests.swift
#	Tests/ComposableArchitectureTests/ReducerTests.swift
#	Tests/ComposableArchitectureTests/RuntimeWarningTests.swift
#	Tests/ComposableArchitectureTests/StoreTests.swift
#	Tests/ComposableArchitectureTests/TestStoreFailureTests.swift
#	Tests/ComposableArchitectureTests/TestStoreTests.swift
#	Tests/ComposableArchitectureTests/TimerTests.swift
#	Tests/ComposableArchitectureTests/ViewStoreTests.swift
  • Loading branch information
mbrandonw authored and p4checo committed Nov 7, 2022
1 parent 71c1c0f commit 2550020
Show file tree
Hide file tree
Showing 157 changed files with 9,492 additions and 5,731 deletions.
4 changes: 4 additions & 0 deletions Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.UIKitCaseStudies;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -949,6 +950,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.UIKitCaseStudies;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -1121,6 +1123,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SwiftUICaseStudies;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -1139,6 +1142,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SwiftUICaseStudies;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down
38 changes: 21 additions & 17 deletions Examples/CaseStudies/SwiftUICaseStudies/00-Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,31 @@ enum RootAction {
}

struct RootEnvironment {
var date: () -> Date
var date: @Sendable () -> Date
var downloadClient: DownloadClient
var fact: FactClient
var favorite: (UUID, Bool) -> Effect<Bool, Error>
var fetchNumber: () -> Effect<Int, Never>
var favorite: @Sendable (UUID, Bool) async throws -> Bool
var fetchNumber: @Sendable () async throws -> Int
var mainQueue: DateScheduler
var notificationCenter: NotificationCenter
var uuid: () -> UUID
var screenshots: @Sendable () async -> AsyncStream<Void>
var uuid: @Sendable () -> UUID
var webSocket: WebSocketClient

static let live = Self(
date: Date.init,
date: { Date() },
downloadClient: .live,
fact: .live,
favorite: favorite(id:isFavorite:),
fetchNumber: liveFetchNumber,
mainQueue: QueueScheduler.main,
notificationCenter: .default,
uuid: UUID.init,
screenshots: { @MainActor in
AsyncStream(
NotificationCenter.default
.notifications(named: UIApplication.userDidTakeScreenshotNotification)
.map { _ in }
)
},
uuid: { UUID() },
webSocket: .live
)
}
Expand Down Expand Up @@ -146,13 +152,13 @@ let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
.pullback(
state: \.effectsCancellation,
action: /RootAction.effectsCancellation,
environment: { .init(fact: $0.fact, mainQueue: $0.mainQueue) }
environment: { .init(fact: $0.fact) }
),
episodesReducer
.pullback(
state: \.episodes,
action: /RootAction.episodes,
environment: { .init(favorite: $0.favorite, mainQueue: $0.mainQueue) }
environment: { .init(favorite: $0.favorite) }
),
focusDemoReducer
.pullback(
Expand Down Expand Up @@ -188,7 +194,7 @@ let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
.pullback(
state: \.longLivingEffects,
action: /RootAction.longLivingEffects,
environment: { .init(notificationCenter: $0.notificationCenter) }
environment: { .init(screenshots: $0.screenshots) }
),
mapAppReducer
.pullback(
Expand Down Expand Up @@ -243,9 +249,7 @@ let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
.pullback(
state: \.refreshable,
action: /RootAction.refreshable,
environment: {
.init(fact: $0.fact, mainQueue: $0.mainQueue)
}
environment: { .init(fact: $0.fact, mainQueue: $0.mainQueue) }
),
sharedStateReducer
.pullback(
Expand Down Expand Up @@ -275,7 +279,7 @@ let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
.debug()
.signpost()

private func liveFetchNumber() -> Effect<Int, Never> {
Effect.deferred { Effect(value: Int.random(in: 1...1_000)) }
.delay(1, on: QueueScheduler.main)
@Sendable private func liveFetchNumber() async throws -> Int {
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
return Int.random(in: 1...1_000)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ComposableArchitecture
import ReactiveSwift
import SwiftUI
@preconcurrency import SwiftUI // NB: SwiftUI.Color and SwiftUI.Animation are not Sendable yet.

private let readMe = """
This screen demonstrates how changes to application state can drive animations. Because the \
Expand All @@ -19,32 +19,14 @@ private let readMe = """
toggle at the bottom of the screen.
"""

extension Effect where Error == Never {
public static func keyFrames(
values: [(output: Value, duration: TimeInterval)],
scheduler: DateScheduler
) -> Self {
.concatenate(
values
.enumerated()
.map { index, animationState in
index == 0
? Effect(value: animationState.output)
: Effect(value: animationState.output)
.delay(values[index - 1].duration, on: scheduler)
}
)
}
}

struct AnimationsState: Equatable {
var alert: AlertState<AnimationsAction>?
var circleCenter: CGPoint?
var circleColor = Color.black
var isCircleScaled = false
}

enum AnimationsAction: Equatable {
enum AnimationsAction: Equatable, Sendable {
case alertDismissed
case circleScaleToggleChanged(Bool)
case rainbowButtonTapped
Expand Down Expand Up @@ -72,11 +54,12 @@ let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnv
return .none

case .rainbowButtonTapped:
return .keyFrames(
values: [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .black]
.map { (output: .setColor($0), duration: 1) },
scheduler: environment.mainQueue.animation(.linear)
)
return .run { send in
for color in [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .black] {
await send(.setColor(color), animation: .linear)
try await environment.mainQueue.sleep(for: 1)
}
}
.cancellable(id: CancelID.self)

case .resetButtonTapped:
Expand Down
28 changes: 23 additions & 5 deletions Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Basics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ struct EffectsBasicsState: Equatable {

enum EffectsBasicsAction: Equatable {
case decrementButtonTapped
case decrementDelayResponse
case incrementButtonTapped
case numberFactButtonTapped
case numberFactResponse(Result<String, FactClient.Failure>)
case numberFactResponse(TaskResult<String>)
}

struct EffectsBasicsEnvironment {
Expand All @@ -47,25 +48,42 @@ let effectsBasicsReducer = Reducer<
EffectsBasicsAction,
EffectsBasicsEnvironment
> { state, action, environment in
enum DelayID {}

switch action {
case .decrementButtonTapped:
state.count -= 1
state.numberFact = nil
// Return an effect that re-increments the count after 1 second if the count is negative
return state.count >= 0
? .none
: .task {
try await environment.mainQueue.sleep(for: 1)
return .decrementDelayResponse
}
.cancellable(id: DelayID.self)

case .decrementDelayResponse:
if state.count < 0 {
state.count += 1
}
return .none

case .incrementButtonTapped:
state.count += 1
state.numberFact = nil
return .none
return state.count >= 0
? .cancel(id: DelayID.self)
: .none

case .numberFactButtonTapped:
state.isNumberFactRequestInFlight = true
state.numberFact = nil
// Return an effect that fetches a number fact from the API and returns the
// value back to the reducer's `numberFactResponse` action.
return environment.fact.fetch(state.count)
.observe(on: environment.mainQueue)
.catchToEffect(EffectsBasicsAction.numberFactResponse)
return .task { [count = state.count] in
await .numberFactResponse(TaskResult { try await environment.fact.fetch(count) })
}

case let .numberFactResponse(.success(response)):
state.isNumberFactRequestInFlight = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,19 @@ private let readMe = """

struct EffectsCancellationState: Equatable {
var count = 0
var currentTrivia: String?
var isTriviaRequestInFlight = false
var currentFact: String?
var isFactRequestInFlight = false
}

enum EffectsCancellationAction: Equatable {
case cancelButtonTapped
case stepperChanged(Int)
case triviaButtonTapped
case triviaResponse(Result<String, FactClient.Failure>)
case factButtonTapped
case factResponse(TaskResult<String>)
}

struct EffectsCancellationEnvironment {
var fact: FactClient
var mainQueue: DateScheduler
}

// MARK: - Business logic
Expand All @@ -40,35 +39,35 @@ let effectsCancellationReducer = Reducer<
EffectsCancellationState, EffectsCancellationAction, EffectsCancellationEnvironment
> { state, action, environment in

enum TriviaRequestId {}
enum NumberFactRequestID {}

switch action {
case .cancelButtonTapped:
state.isTriviaRequestInFlight = false
return .cancel(id: TriviaRequestId.self)
state.isFactRequestInFlight = false
return .cancel(id: NumberFactRequestID.self)

case let .stepperChanged(value):
state.count = value
state.currentTrivia = nil
state.isTriviaRequestInFlight = false
return .cancel(id: TriviaRequestId.self)

case .triviaButtonTapped:
state.currentTrivia = nil
state.isTriviaRequestInFlight = true

return environment.fact.fetch(state.count)
.observe(on: environment.mainQueue)
.catchToEffect(EffectsCancellationAction.triviaResponse)
.cancellable(id: TriviaRequestId.self)

case let .triviaResponse(.success(response)):
state.isTriviaRequestInFlight = false
state.currentTrivia = response
state.currentFact = nil
state.isFactRequestInFlight = false
return .cancel(id: NumberFactRequestID.self)

case .factButtonTapped:
state.currentFact = nil
state.isFactRequestInFlight = true

return .task { [count = state.count] in
await .factResponse(TaskResult { try await environment.fact.fetch(count) })
}
.cancellable(id: NumberFactRequestID.self)

case let .factResponse(.success(response)):
state.isFactRequestInFlight = false
state.currentFact = response
return .none

case .triviaResponse(.failure):
state.isTriviaRequestInFlight = false
case .factResponse(.failure):
state.isFactRequestInFlight = false
return .none
}
}
Expand All @@ -91,7 +90,7 @@ struct EffectsCancellationView: View {
value: viewStore.binding(get: \.count, send: EffectsCancellationAction.stepperChanged)
)

if viewStore.isTriviaRequestInFlight {
if viewStore.isFactRequestInFlight {
HStack {
Button("Cancel") { viewStore.send(.cancelButtonTapped) }
Spacer()
Expand All @@ -101,11 +100,11 @@ struct EffectsCancellationView: View {
.id(UUID())
}
} else {
Button("Number fact") { viewStore.send(.triviaButtonTapped) }
.disabled(viewStore.isTriviaRequestInFlight)
Button("Number fact") { viewStore.send(.factButtonTapped) }
.disabled(viewStore.isFactRequestInFlight)
}

viewStore.currentTrivia.map {
viewStore.currentFact.map {
Text($0).padding(.vertical, 8)
}
}
Expand Down Expand Up @@ -134,8 +133,7 @@ struct EffectsCancellation_Previews: PreviewProvider {
initialState: EffectsCancellationState(),
reducer: effectsCancellationReducer,
environment: EffectsCancellationEnvironment(
fact: .live,
mainQueue: QueueScheduler.main
fact: .live
)
)
)
Expand Down
Loading

0 comments on commit 2550020

Please sign in to comment.