StevenLambion / SwiftDux

Predictable state management for SwiftUI applications.
http://stevenlambion.github.io/SwiftDux
MIT License
153 stars 9 forks source link

Views need to know about dispatched Actions #17

Closed lightandshadow68 closed 4 years ago

lightandshadow68 commented 4 years ago

A common pattern is that the result of network requests or database queries will dispatch actions to the store to update some state. This abstracts the UI from having knowledge of services are responsible for setting the state.

However, by default, a view doesn't update if it does not dispatch an action. To get the view to reflect state changes in the above scenarios, it has to implement the updateWhen() protocol method. While this is simpler than trying specify which part of the state to observe, which is tricky in ReSwift, it does require the View to check for (and therefore know about) specific actions that are dispatched by other services. This seems like an implementation detail the Views shouldn't know about.

Perhaps this is something that can be resolved by dispatching generic actions though middleware?

StevenLambion commented 4 years ago

Yeah, that's been one of the major concerns with SwiftUI. Even Apple's own examples originally had you manually tell the observer that an update happened via a property observer or at the end of a function call (Similar to a reducer function). They've since added synthesized functionality to the compiler to help handle it automatically.

My "manual" way was based on a similar strategy due to limitations with SwiftUI. It led me to use actions as the update trigger. I'm not a big fan of views specifying what actions cause them to update, so I added a default implementation that is triggered when they dispatch their own actions. I then added the updateWhen() functionality as a catch-all compromise.

Another possible route I originally looked at was referential checking using classes for state, but that has a whole host of other problems. I also prefer using structs for state.

I'd definitely be glad to investigate other ways to handle this problem or if other people have a good solution.

In the meantime, an action plan might work in this case. You can dispatch an action plan with a service call, and any actions dispatched within it should update the original view. If this isn't working correctly, it could be a bug.

lightandshadow68 commented 4 years ago

In my most recent tvOS app, I used an app controller architecture with ReSwift. Since each app controller could only subscribe to one part of the state, I found myself using composition w/ objects that subscribed to different substates of the store. The ability to check for multiple actions helps simplify this, but at the expense of requiring the view to know about more than the substates of the store, which it must know about regardless.

A strategy I'm leaning towards with SwiftDux to use middleware to map specific service actions to a limited number of generic actions that SwiftUI view are aware of to cause updates. In many uses cases, services might dispatch actions completely independent of the UI, such as a background refresh or a live query, which means there is no original view to update.

StevenLambion commented 4 years ago

It's definitely a common use case to dispatch actions from services or other types of APIs. I've done a little bit of that with the the persister middleware to reset the state, but it's still handled in the StoreProviderViewModifier by checking the action's type.

Using some kind of middleware with a generic action sounds interesting. There would need to be some way to target the specific observers that need to be updated. If you don't mind providing a quick example of your idea, that would awesome.

lightandshadow68 commented 4 years ago

I was planning on having middleware dispatche a generic, view-aware action when loading had started on ended and use that to update the content. Not all views need a loading indicator, so I might have to contrive something in those cases, but mapping between views and data sources can happen in middleware instead of making the data source know about the view or vice versa.

StevenLambion commented 4 years ago

With v1.0.0, State is now required to be equatable for change tracking. This removes the need for the update function to track external actions.