Replies: 1 comment 1 reply
-
First of all I'm really sorry for the outdated docs. I'll fix that asap. Second, correct, the Middleware base class is now deprecated and the new The idea behind You should avoid storing the output or getState directly, but they can be kept alive by closures or Cancellables. For example, using Combine you could have something like this: class AppLifecycleMiddleware: MiddlewareProtocol {
typealias InputActionType = AppLifecycleAction
typealias OutputActionType = AppLifecycleAction
typealias StateType = Void
private var cancellable: AnyCancellable?
func handle(action: InputActionType, from dispatcher: ActionSource, state: @escaping GetState<StateType>) -> IO<OutputActionType> {
switch action {
// for this, we need to observe the NotificationCenter, which implies in performing side-effects
case .start:
return handleStart()
// for these two, we don't trigger any side-effect and we don't dispatch any more actions
case .didEnterBackground, .willEnterForeground:
return .pure()
}
}
private func handleStart() -> IO<OutputActionType> {
// prepare the whole thing, but don't start the side-effect yet (only `sink` starts it in Combine)
let nc = NotificationCenter.default
let foregroundPublisher = nc
.publisher(for: UIApplication.willEnterForegroundNotification)
.map { _ in AppLifecycleAction.willEnterForeground }
let backgroundPublisher = nc
.publisher(for: UIApplication.didEnterBackgroundNotification)
.map { _ in AppLifecycleAction.didEnterBackground }
let allNotifications = Publishers.Merge(foregroundPublisher, backgroundPublisher)
return IO { [weak self] output in
// ok, we are inside IO, we have the freedom to do whatever we want in terms of side-effects now
// as we prepared the Combine pipeline, let's kick-off the observation and use the output to emit actions
self?.cancellable = allNotifications
.print()
// the `sink` closure will keep the output alive, the `cancellable` variable will keep the Subscription with sink alive
.sink(receiveValue: { output.dispatch($0) })
}
}
} With IO middleware you can use Combine, RxSwift, async/await, NotificationCenter, delegates or whatever method you want to keep the side-effect observation and the output alive. However, if you are planning to use Combine or RxSwift more in depth, the EffectMiddleware might be a better option for you. Same example here: extension EffectMiddleware where InputActionType == AppLifecycleAction, OutputActionType == AppLifecycleAction, StateType == Void, Dependencies == AppLifecycleMiddlewareDependencies {
static func appLifecycle() -> MiddlewareReader<Dependencies, EffectMiddleware> {
EffectMiddleware.onAction { action, _, _ in
switch action {
case .start:
return handleStart()
case .didEnterBackground, .willEnterForeground:
return .doNothing
}
}
}
private static func handleStart() -> Effect<AppLifecycleMiddlewareDependencies, AppLifecycleAction> {
Effect(token: "notification_center_observation") { context in
let foregroundPublisher = context.dependencies.notificationCenter(UIApplication.willEnterForegroundNotification)
.eraseToAnyPublisher()
.map { _ in AppLifecycleAction.willEnterForeground }
let backgroundPublisher = context.dependencies.notificationCenter(UIApplication.didEnterBackgroundNotification)
.eraseToAnyPublisher()
.map { _ in AppLifecycleAction.didEnterBackground }
return Publishers
.Merge(foregroundPublisher, backgroundPublisher)
.print()
.map { DispatchedAction.init($0) }
}
}
}
// the dependency injection part:
struct AppLifecycleMiddlewareDependencies {
let notificationCenter: (Notification.Name) -> any Publisher<Notification, Never>
}
struct World {
let notificationCenter: (Notification.Name) -> any Publisher<Notification, Never>
}
extension World {
static var live: World {
.init(
notificationCenter: { NotificationCenter.default.publisher(for: $0) }
)
}
}
// and you use it like this:
let store = Store(
subject: .combine(initialValue: .identity),
reducer: appReducer,
middleware:
EffectMiddleware
.appLifecycle()
.lift(
inputAction: \AppAction.lifecycle,
outputAction: AppAction.lifecycle,
state: ignore,
dependencies: { AppLifecycleMiddlewareDependencies(notificationCenter: $0.notificationCenter) }
).inject(world)
)
store.dispatch(.lifecycle(.start)) Another benefit is that EffectMiddleware comes with dependency injection management for free ( |
Beta Was this translation helpful? Give feedback.
-
In the quick guide AppLifeCycleMiddleware example you give the following:
`
`
I noticed the current code has deprecated Middleware in favor of MiddlewareProtocol. What has me confused is that the documentation of receiveContext() it says the following
@available( *, deprecated, message: """ Instead of relying on receiveContext, please use the getState from handle(action) function, and when returning IO from the same handle(action) function use the output from the closure """ ) func receiveContext(getState: @escaping GetState<StateType>, output: AnyActionHandler<OutputActionType>)
So my question to you is how do we convert the old middleware example to work with the new MiddlewareProtocol along with the restriction of not using receiveContext()?
The best I could think of is this:
`
`
Is this right or is their a better way to do this? I'd ask on your slack but I the link was not working.
Beta Was this translation helpful? Give feedback.
All reactions