Hi!
I’m struggling a bit with data flows in SwiftUI. Most SwiftUI sample apps I’ve seen use one large app model / store and inject that into the environment.
That works for small samples, but I’m not sure how this should scale in a real app where the data is naturally split into multiple models/services.
For example, I may have separate types for things like:
@Observable final class AuthModel { ... }
@Observable final class MediaSourcesModel { ... }
@Observable final class UsersModel { ... }
final class HTTPClient { ... }
Some of these only make sense once the user is logged in. For example, media sources, user data, and the HTTP client may all depend on the current user/session/token.
What I’m struggling with is where these objects should be owned and created in a SwiftUI app.
I’m trying to avoid creating them directly inside a view body, because that can recreate them as the view updates. I’m also unsure whether putting this setup in custom view initializers is the right direction, since SwiftUI views are lightweight and can be reconstructed.
What is the recommended ownership / data-flow pattern for this kind of setup?
More specifically, how should a SwiftUI app usually handle several separate models that depend on login state, without turning everything into one large global model?
Thank you!
There are a number of ways you can handle large apps with multiple dependencies like this! SwiftUI is architecture agnostic, meaning there are many reasonable ways to model your data. A good place to start is likely putting them in the environment as you've seen other apps do with a single large model, and just keeping them separated!
There's nothing bad about having multiple observables that you inject into the environment make up part of your data model. If anything, it may help you design your views such that they only declare dependencies they need to have.
Doing this will also handle the other problem you mentioned, that not all of these models will be created initially. For example, say UsersModel only populates after login. You could imagine the view which injects that into the environment would be lower down the view hierarchy, and could look something like:
struct LoginView: View {
@Environment(\.authModel) var authModel
@State var usersModel: UsersModel = nil
var body: some View {
Button("Login") {
Task {
await authModel.login()
usersModel = await authModel.getUsers()
}
}
MainAppView()
.environment(\.usersModel, usersModel)
}
}