Vlados
(Vlado Scesnak)
1
Hi,
thanks for TCA I was struggling with some SwiftUI related things, but TCA make it more easier ! I just have question about reducer.
I want to use SwitchLet to define if is user logged in or logged out. I'm observing some UseCase which returns PassthrougSubject<User?, Never>. But I don't know how to make it properly and I'm getting Member 'loggedOut' expects argument of type 'RootLoggedOutAction'.
What I tried:
Reducer { state, action, env in
env.observeUserChanges.invoke()
.flatMap { user -> AnyPublisher<AppAction, Never> in
guard let user = user else {
return Just(.loggedOut).eraseToAnyPublisher()
}
return Just(.loggedIn).eraseToAnyPublisher()
}
.eraseToEffect()
}
Where AppState:
enum AppState: Equatable {
case loggedIn(RootLoggedInState)
case loggedOut(RootLoggedOutState)
public init() { self = .loggedOut(.init()) }
}
AppAction:
enum AppAction: Equatable {
case loggedIn(RootLoggedInAction)
case loggedOut(RootLoggedOutAction)
}
Full appReducer:
let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
rootLoggedInReducer.pullback(
state: /AppState.loggedIn,
action: /AppAction.loggedIn,
environment: { env in env }
),
rootLoggedOutReducer.pullback(
state: /AppState.loggedOut,
action: /AppAction.loggedOut,
environment: { env in env }
),
Reducer { state, action, env in
env.observeUserChanges.invoke()
.flatMap { user -> AnyPublisher<AppAction, Never> in
guard let user = user else {
return Just(.loggedOut).eraseToAnyPublisher()
}
return Just(.loggedIn).eraseToAnyPublisher()
}
.eraseToEffect()
}
)
Can I reduce it to Effect just from environment, or do I need to do it in some other way?
I'm sorry, if it is smaller Swift problem, but I am Android developer and I am trying to learn Swift :D
Thanks,
Vlado
Malauch
2
Check the types of both Just publishers. Output of these is not AppAction.
let publisher1 = Just(.loggedOut) // Just<(RootLoogedOutAction) -> AppAction>
let publisher2 = Just(.loggedIn) // Just<(RootLoogedInAction) -> AppAction>
You are not providing associated values for AppAction enum cases, so they are function from associated value to enum value.
However, I guess that you really don't want to start new long running observing effect on every action sent to appReducer. Additionally, I would recommend to always send side effect with explicit action or in with higher order reducer.
Personally I would define new AppActions to handle monitoring user status and updating state.
enum AppAction: Equatable {
case loggedIn(RootLoggedInAction)
case loggedOut(RootLoggedOutAction)
case startObservingUserStatus
case updateState(AppState)
}
let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
rootLoggedInReducer.pullback(
state: /AppState.loggedIn,
action: /AppAction.loggedIn,
environment: { env in env }
),
rootLoggedOutReducer.pullback(
state: /AppState.loggedOut,
action: /AppAction.loggedOut,
environment: { env in env }
),
.init { state, action, env in
switch action {
case .startObservingUserStatus:
return env.userStatusObserver() // Side effect which monitors user status and publish relevant state
.map(AppAction.updateState) // maps state to AppAction
case let .updateState(newState):
state = newState // updates state
return .none
case .loggedIn:
return .none
case .loggedIn:
return .none
}
)
You should probably additionally handle cancelling this observing effect. You can send .startObservingUserStatus in appDidFinishLunching function or in .onAppear / .task view modifier.
Important disclaimer: I'm also beginner, so please forgive me if I made some mistake or I misunderstood your problem.