Ivan C Myrvold
Ivan C Myrvold

Reputation: 840

Reference EnvironmentObject in ObservableObject

I have a LoginView that shows a RegisterView if the user is not logged in, and a ContentView if he is logged in:

struct LoginView: View {
    @EnvironmentObject var userManager: UserManager
    var body: some View {
        Group {
            if userManager.isRegistered {
                ContentView()
            } else {
                RegisterView()
            }
        }
    }
}

ContentView have three ObservedObject properties, that uses combine to fetch content from a server with rest api's.

struct ContentView: View {
    @EnvironmentObject var userManager: UserManager
    @StateObject var usersStore = UsersStore()
    @StateObject var rolesStore = RolesStore()
    @StateObject var elementsStore = ElementsStore()

    var body: some View {
        NavigationView {
            ZStack {
                Image("stell")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .opacity(0.1)
            
                VStack(alignment: .leading, spacing: 5) {
                    NavigationLink(destination: UsersView(usersStore: usersStore) ) {
                        Text("Users")
                    }
                    NavigationLink(destination: RolesView(rolesStore: rolesStore)) {
                        Text("Roles")
                    }
                    NavigationLink(destination: ElementsView(elements: $elementsStore.elements)) {
                        Text("Elements")
                    }
                }.font(.title).padding(20)
            }.navigationBarTitle(Text("STELL"))
        }
    }
}

The problem I have is that I want to reference userManager from any of the observedObjects, e.g. when the rest api's returns 401 Unauthorized when the session token has expired. Then I want the ObservedObject to set the isRegistered flag in userManager to false so the RegisterView is automatically shown. But how can I do that? I can't set a reference to userManager in any of the ObservedObject property initializers, because the compiler complains about property initializers is run before self is available.

Upvotes: 8

Views: 2411

Answers (2)

malhal
malhal

Reputation: 30773

As long as you don't mutate any state you can do that from body, e.g.

struct ContentView: View {
    @EnvironmentObject var userManager: UserManager
    @StateObject var usersStore = UsersStore()
    @StateObject var rolesStore = RolesStore()
    @StateObject var elementsStore = ElementsStore()

    func configureObjects(){
        usersStore.userManager = userManager
        rolesStore.userManager = userManager
        elementsStore.userManager = userManager
    }

    var body: some View {
        let _ = configureObjects()
        NavigationView {

And in these objects you probably want to reset things back to nil in the didSet of the stores (if the userManger is different object from last time) and then set things up again asynchronously or lazily when a getter is accessed from body. Also you might want to move the @StateObject into a custom View that actually accesses its properties, so you have less redundant invalidations of ContentView.

Upvotes: 0

Asperi
Asperi

Reputation: 258441

I would use in this case dependency injection via constructor... below is show possible approach on example of UsersStore, for others it would be the same

Changes in UsersStore

class UsersStore: ObservableObject {
    var manager: UserManager
    
    init(manager: UserManager) { // << inject UserManager via constructor
        self.manager = manager 
    }
    ...
}

Changes in ContentView

struct ContentView: View {
    @EnvironmentObject var userManager: UserManager
    @ObservedObject var usersStore: UsersStore

    init(usersStore: UsersStore) {
        self.usersStore = usersStore // << inject UsersStore via contructor
    }

Changes in usage

struct LoginView: View {
    @EnvironmentObject var userManager: UserManager
    var body: some View {
        Group {
            if userManager.isRegistered {
                // userManager is valid here some UsersStore can be created
                ContentView(usersStore: UsersStore(manager: self.userManager))

Upvotes: 8

Related Questions