I create my Store
in the scene delegate, and save a reference to it. Even though I have a reference to it, I'm currently stuck on not being able to send actions to that reference.
For example, I would like to send an action when the app enters the background. I thought I would be able to do this via sending it an action, but the send
method is not exposed to the outside world.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let appStore = Store<AppState, AppAction> = ...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = AppView(store: appStore)
// ...
window.rootViewController = UIHostingController(rootView: contentView)
// ...
}
func sceneDidEnterBackground(_ scene: UIScene) {
appStore.send(.didEnterBackground) // ❌ Does not compile as `send` is not public
}
}
Sending actions from the scene delegate is just one example, I could imagine that in the future I have other singletons/classes that would like to emit global events to the store as well.
What's the best way to send these sorts of actions?
One idea is to create a PassthroughSubject environment object which can take in any arbitrary actions. However that has two problems:
- This seems like an anti-pattern to expose something that doesn't seem like it wants to be exposed
- I don't see a good way to initialize the listener (e.g. typically I initialize the listeners in the view's onAppear method, but if I want the passthrough action to be
sceneWillEnterForeground
, this is fired before onAppear happens). Here's an example to illustrate the problem
struct AppEnvironment {
var actionsPassthrough: PassthroughSubject<AppAction, Never> = .init()
}
let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in
switch action {
case .onAppear: // called when AppView appears
if state.isFirstOnAppear {
state.isFirstOnAppear = false
return environment.actionsPassthrough.eraseToEffect()
} else {
return .none
}
case .didEnterForeground: // called on SceneDelegate will enter foreground
// ...
return .none
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let appEnvironment = AppEnvironment()
// ...
func sceneWillEnterForeground(_ scene: UIScene) {
// ❌ Does not emit the first time since the .onAppear event hasn't fired yet
appEnvironment.actionsPassthrough.send(.didEnterForeground)
}
}
In the above example, ideally the first WillEnterForeground would fire, but it doesn't because the order of operations is:
-
.didEnterForeground
: no-op since onAppear hasn't happened yet. -
.onAppear
: registers to listen foractionsPassthrough
events.
Only subsequent .didEnterForeground
will actually fire the event.