You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SwiftUIFlux works great for apps which only need a singleton or two in their service layer, since you can just reference the singletons in AsyncActions to perform network requests etc. For apps with a larger dependency graph, I’ve been thinking about how to connect async actions to services without relying on singletons — and at the same time trying to avoid the age-old pitfall of creating another giant complex dependency injection system, or turning SwiftUIFlux into something much more complex than it needs to be.
I wanted something where I could wire up my dependency graph as usual, and handle SwiftUIFlux Actions in one place:
actions.publisher(for:LogIn.self)// Catch a `LogIn` action.flatMapResult(userAuthenticator.logIn)// Log in using `userAuthenticator` and return a `Result`.sink(receiveValue: store.dispatch)// Dispatch the result back to the store.store(in:&subscriptions)
I put together a simple middleware which provides combine publishers for actions. The idea is you catch these actions and map them to service calls. The neat part is if you have your services also return Action publishers, you can then connect them back to the store for further dispatch.
Here’s a more full example showing an app with two dependencies (my app’s real dependency graph is obviously more complex and is several layers deep):
classAppContainer{letstore:Store<AppState>privateletactions:ActionPublisherMiddlewareprivateletuserAuthenticator:UserAuthenticatorprivateletsoundEffectsPlayer:SoundEffectsPlayerprivatevarsubscriptions:[AnyCancellable]=[]init(){
actions =ActionPublisherMiddleware()
userAuthenticator =UserAuthenticator(/* Some dependency... */)
soundEffectsPlayer =SoundEffectsPlayer(/* Some other dependency... */)
store =AppStore(reducer: appReducer, middleware:[actions.handler], state:AppState())// Subscriptions
actions.publisher(for:LogIn.self).flatMapResult(userAuthenticator.logIn).sink(receiveValue: store.dispatch).store(in:&subscriptions)
actions.publisher(for:LogInDidSucceed.self).map{ _ inSoundEffectsPlayer.Sound.bing }.sink(receiveValue: soundEffectsPlayer.play).store(in:&subscriptions)
actions.publisher(for:LogOut.self).flatMapResult(userAuthenticator.logOut).sink(receiveValue: store.dispatch).store(in:&subscriptions)}}
The middleware is trivial, and you could arrange it however you like (say as a free function with a global publisher if you preferred). I’ve done it as a class with a handler function:
classActionPublisherMiddleware{privateletpublisher=PassthroughSubject<Action,Never>()func publisher<A>(for action:A.Type)->AnyPublisher<A,Never>where A :Action{
publisher
.compactMap{ $0 as?A}.eraseToAnyPublisher()}func handler(dispatch:@escapingDispatchFunction, getState:@escaping()->FluxState?)->(@escapingDispatchFunction)->DispatchFunction{return{ next inreturn{ action inself.publisher.send(action)returnnext(action)}}}}
The neat behaviour where the results of async service calls can be mapped back to the store dispatcher is also trivial — I used an overload on Store.dispatch that just dispatches from a result type where both cases are actions:
extensionStore{func dispatch<S,F>(result:Result<S,F>)where S :Action, F :Action{
switch result {case.success(let action):dispatch(action: action)case.failure(let action):dispatch(action: action)}}}
I’ve started structuring my services to take and return actions. Actions are just plain values after all so it doesn’t pose any real problem for them to make their way into the service layer. Here’s an example:
I would be interested to hear if anyone else has also done something like the above. Are there any parts of this that would be suitable for inclusion in SwiftUIFlux? Would the Combine publisher middleware be general enough to be attached to the store (eg. store.publisher(for: <Action>.self)...)?
The text was updated successfully, but these errors were encountered:
SwiftUIFlux works great for apps which only need a singleton or two in their service layer, since you can just reference the singletons in
AsyncAction
s to perform network requests etc. For apps with a larger dependency graph, I’ve been thinking about how to connect async actions to services without relying on singletons — and at the same time trying to avoid the age-old pitfall of creating another giant complex dependency injection system, or turning SwiftUIFlux into something much more complex than it needs to be.I wanted something where I could wire up my dependency graph as usual, and handle SwiftUIFlux
Action
s in one place:I put together a simple middleware which provides combine publishers for actions. The idea is you catch these actions and map them to service calls. The neat part is if you have your services also return
Action
publishers, you can then connect them back to the store for further dispatch.Here’s a more full example showing an app with two dependencies (my app’s real dependency graph is obviously more complex and is several layers deep):
The middleware is trivial, and you could arrange it however you like (say as a free function with a global publisher if you preferred). I’ve done it as a class with a handler function:
The neat behaviour where the results of async service calls can be mapped back to the store dispatcher is also trivial — I used an overload on
Store.dispatch
that just dispatches from a result type where both cases are actions:I’ve started structuring my services to take and return actions. Actions are just plain values after all so it doesn’t pose any real problem for them to make their way into the service layer. Here’s an example:
I would be interested to hear if anyone else has also done something like the above. Are there any parts of this that would be suitable for inclusion in SwiftUIFlux? Would the Combine publisher middleware be general enough to be attached to the store (eg.
store.publisher(for: <Action>.self)...
)?The text was updated successfully, but these errors were encountered: