I'm trying to convert all my @EnvironmentObject
to @Environment
and I ran into an issue. The UserService
has a property user
which is nil
. After that a network call takes place and the user gets updated. These changes get published in the View but unlike before, .onAppear()
is not triggered again. I need this because once I have the user
I need to call a method in the ViewModel which needs the user.
This used to work fine before but I guess something under the hood must have changed. Is there a new way to handle this?
@Observable class UserService {
var user: User?
var handle: AuthStateDidChangeListenerHandle?
init() {
print("UserService int()")
}
func listen() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
self.getCurrentUser(uid: user?.uid)
})
}
func getCurrentUser(uid: String?) {
guard let uid = uid else { return }
Firestore.firestore().collection("users").document(uid).getDocument { documentSnapshot, error in
do {
self.user = try documentSnapshot?.data(as: User.self)
print("getCurrentUser", self.user?.displayName)
} catch {
print("error")
self.error = error.localizedDescription
}
}
}
}
struct DiscoverView: View {
@Environment(UserService.self) private var userService
var body: some View {
// OK gets updated when user is updated
Text(userService.user?.displayName ?? "nil")
.onAppear() {
print("onAppear")
if userService?.user {
//trigger something else
}
// will trigger once and wont trigger when user is updated
}
}
}
The fact that onAppear
is only called once in your case is actually the correct behavior and how it should be.
Only if the (structural) identity of a SwiftUI view were to change would onAppear
also be called multiple times. A change to a state of a view does not result in multiple calls to onAppear
.
To observe the change of User
in your case, the correct way would be to use onChange
:
Text(userService.user?.displayName ?? "nil")
.onChange(of: userService.user) { oldUser, newUser in
print("User changed: \(newUser?.displayName)")
}
.onAppear() {
print("onAppear")
}
If onAppear
was called multiple times in your old code, this could actually indicate a problem with your view hierarchies and the SwiftUI state handling.
The use of conditional view modifiers, for example, can lead to this issue.