Biiiiiird
Biiiiiird

Reputation: 394

How to share data between models in SwiftUI?

I have an EnvironmentObject "User" that is responsible for sign-in and fetching user data. It's shared between all views in my app. Once the user data is loaded, the app changes views from "LoginView" to "HomeView." This User object contains data "userId."

I have another model called "Alerts" that should be initialized in the HomeView. Alerts will fetch alerts from the server, but it needs the userId from User to actually fetch. How can I share this data from User to Alert?

struct HomeView: View {
    @EnvironmentObject var user: User
    @ObservedObject var alerts: Alerts
        
    init() {
        if let id = user.id {
            self.alerts = Alerts(userId: id)
        }
    }

Above I get the error 'self' used before all stored properties are initialized. I believe there is something fundamental here that I am not understanding about SwiftUI or my model designs are problematic (each model is capable of using an API class to make calls to the server).

Edit: I've found a similar question here:

Swiftui - How do I initialize an observedObject using an environmentobject as a parameter?

But the answer (to create a nested view) seems hacky. Is this what SwiftUI intended or is there a more proper pattern used to accomplish this?

Upvotes: 0

Views: 1674

Answers (1)

Biiiiiird
Biiiiiird

Reputation: 394

I've found two solutions to my problem and have opted for the second one listed below.

Option 1: Nest views so that "Alerts" is initialized in the body of the parent view

This solution is detailed in this Stack Overflow question.

This solution seems a bit hacky, but maybe I'm misguided in that thinking and someone can correct this sentiment. Regardless, here is a quick code snippet:

struct HomeView: View {
    @EnvironmentObject var user: User
    
    var body: some View {
        HomeInternalView(Alerts(userId: user.id))
    }
}

struct HomeInternalView: View {
    @ObservedObject alerts: Alerts

    init(alerts) {
        self.alerts = alerts
    }

    var body: some View {
       // ...
    }
}

Option 2: Make User into a singleton

For my specific case, I realized that the "User" model is needed for every view in the app as well as some models (Alerts). So instead of passing an environment object to every view, I now have a "UserService" and I can access the UserService directly from Alerts.

As per this Stack Overflow question, if a model is needed for every view in the app then using a singleton is acceptable.

class UserService: ObservableObject {
    static let shared: UserService = UserService()
    
    private init() {
        // initializations ..
    }
}

struct HomeView: View {
    @ObservedObject var alerts: Alerts
    @ObservedObject private var user = UserService.shared

    init() {
        self.alerts = Alerts()
    }
    // ...
}

class Alerts: ObservableObject {
    @ObservedObject private var user = UserService.shared
    // ...
}

Upvotes: 1

Related Questions