Sharing (or synchronising) state in multiple navigation stacks #2444
Replies: 2 comments 3 replies
-
Hi @technicated, I personally think the former pattern, where you model the movies/actors/… as a dependency is the way to go. Since this data must be ubiquitous throughout the application, is isn't much different from a database, user defaults, etc. It's worth noting that in our Standups demo app we do not use this style, and instead use the latter pattern you described (that of delegate actions and feature communication to keep state in sync). However, this is only to keep things simple for a simple demo, but in reality it would be far better to model the persisted standups as a dependency. |
Beta Was this translation helpful? Give feedback.
-
Sorry for the delayed response, but sometimes life just gets in the way! I've posted some sample code for this problem and a stub of the solution at https://github.com/technicated/tca-state-sync/tree/main. There's a README in there explaining how I tackled the solution, but the gist is that I restructured the Hope this helps! And I'd like to receive any feedback to see if there's an even better or different way to solve this! Example of some code: struct ActorDetailFeature: Reducer {
struct State: Equatable { ... }
enum Action: BindableAction {
...
case storage(Storage.Action)
...
}
struct Storage: Reducer {
enum Action {
case actorChanged(ObjectChange<Actor>)
case linksChanged(CollectionChange<Link>)
case moviesChanged(CollectionChange<Movie>)
case sync
}
@Dependency(\.storage)
var storage
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
...
case let .actorChanged(.initial(actor)),
let .actorChanged(.inserted(actor)),
let .actorChanged(.modified(actor)):
state.actor = actor
return .none
case let .linksChanged(.changes(_, insertions, _, deletions)):
let actorId = state.actor.id
let movies = storage.movies.all()
state.movies.append(
contentsOf: insertions
.filter { $0.actorId == actorId }
.compactMap { movies[$0.movieId] }
)
deletions
.filter { $0.actorId == actorId }
.forEach { state.movies.remove(id: $0.movieId) }
return .none
...
case .sync:
return .merge(
.run { [actorId = state.actor.id] send in
for await change in storage.actors.observeObject(actorId) {
await send(.actorChanged(change))
}
},
.run { send in
for await change in storage.movies.observeAll() {
await send(.moviesChanged(change))
}
},
.run { send in
for await change in storage.links.observeAll() {
await send(.linksChanged(change))
}
}
)
}
}
}
}
@Dependency(\.storage)
var storage
var body: some ReducerOf<Self> {
Storage()
... main feature reducer ...
}
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello everyone! First things first, thank you Point-Free team and all collaborators for the TCA library and all the great ecosystem that you have created!
I'm a PF subscriber and I have watched almost all episodes, and have tried TCA since its inception for small personal demo projects. I am not new to the ideas and concepts of the library, but I have what appears to be an app design question in mind.
The context is the following: I want to build a sample app to test TCA 1.0, and the app will show a list of movies and a list of actors, and then you can navigate to the detail (of one or the other), and then in the detail there is the name / title (of the actor or the movie) and a list of either the actors starring in the movie or the movies in with the actor has starred. You can continue to drill down as you wish by tapping on an item of these lists.
The screens are:
The act of linking a movie to an actor should be propagated to all existing detail views already on the stack and is also bidirectional i.e. all the presented detail of a movie should list the newly added actor, and all this actor's details should list the newly added movie. The toolbar title should also change to reflect this.
Hoping that the workings of the app is clear, the question is: what's the best way to model this (apparently?) bidirectional state and how to communicate the changes across all screens? Should I use a dependency and offer one or more "on change" endpoints to which each detail listens, or should I use delegate actions and communicate the changes from the detail to root, so that it can update all the detail views pushed on the stacks? Or a combination of both, or anything else? Thank you for you feedback! And if something's not clear ask and I'm going to clarify!
Beta Was this translation helpful? Give feedback.
All reactions