We have now seen two approaches to sharing state in a Composable Architecture application:
You can model everything as value types directly in your features’ states, but then you are left with the difficult problem of keeping a bunch of disconnected values in sync. This most likely means a liberal dose of onChange
reducers and/or delegate actions, and it will be quite easy to get wrong. And also your tests will suffer since you will need to assert on changes to all of those copies of state.
Or you can model your shared state as a dependency, but it takes a bit of boilerplate to accomplish, and worst of all the state isn’t actually held in the feature’s State
struct. It’s only held in the reducer, and so we have to introduce even more boilerplate to listen for changes in the shared state and play them to the feature’s state, just so that the view can get access to that state.
It feels like we’re playing whack-a-mole with shared state. Each attempt solves a problem and then a whole new problem pops up.
However, there is something pretty interesting about the dependency style of shared state that sets it apart from the synchronization style. By using a dependency for our shared state we have in some sense introduced a reference type into our feature. After all, structs with closures are basically a crude approximation of reference types, which is what our UserSettingsClient
type is. This means it behaves like a class for all intents and purposes.
In general, dependencies are very reference-y, and we are seeing the pros and cons of that fact very directly in isowords. The pros are that state can be updated from any place in the entire app and every other part of the app can see those changes immediately. That’s exactly what we want from shared settings state.
But the cons are that there is a lot of setup needed to make the dependency work in state, and it made testing a pain. We had an extra onAppear
and userSettingsUpdated
action to deal with in order to communicate between the dependency and the feature.
So, whether we like it or not, the share state dependency has introduced a reference type in our feature, and if we are OK with that, then my next question would be: could we have just crammed a reference type directly into the feature’s state from the beginning? Wouldn’t that give us the state sharing capabilities without any of the boilerplate of a dependency?
Well, let’s try it out.