I have a class set as ObservableObject to listen to a Firestore collection. Everything works until the app goes asleep (eg. after 30 mins) and a Cloud Function runs to update some data. Then the real time updates no longer happen until I kill and open the app again, only after that I get the most recent updates.
My code is working like this:
class FirebaseRealTime: ObservableObject {
..
@Published var myUsers = [Users]()
..
self.listenToUserCollection()
..
func listenToUserCollection {
db.collection("users").addSnapshotListener { (querySnapshot, error) in
DispatchQueue.global(qos: .background).async {
..
DispatchQueue.main.async {
self.myUsers = tempUsers
//self.usersLoaded = true
}
..
}
}
Then a global var is set in the scene delegate as an environment object
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
..
var userDetails = FirebaseRealTime()
..
let contentView = ViewExample()
.environmentObject(userDetails)
..
}
Last, I have a SwiftUI view receiving the real time data.
struct ViewExample: View {
@EnvironmentObject var userDetails:FirebaseRealTime
@State var users:[Users] = []
var body: some View {
VStack {
ScrollView {
ForEach(users) { user in
RowExample(user: user)
}
}
}
.onReceive(userDetails.$myUsers) { data in
print (data)
}
}
}
As I said when the app is active and I manually change a field in Firestore the data updates, but when the Google Cloud func runs on the backend it does not.
Any idea what’s going on? Is there a way to "force" the received data to get updated, or any other work around?
2
Answers
Reviewing the main issue you are getting, it seems that instead of using
ObservedObject
you should useStateObject
.Since SwiftUI could at any time construct or destroy a view, it is dangerous to create a @
ObservedObject
inside of one. To guarantee consistent results upon a view redraw, utilize the @StateObject wrapper unless you inject the @ObservedObject
as a dependency.The guideline is that whatever view creates your object first must use @
StateObject
to inform SwiftUI that it is the owner of the data and it is in charge of maintaining it. All other views must use @ObservedObject
to indicate to SwiftUI that they want to keep an eye out for changes to the object but do not directly own it.While @
StateObject
and @ObservedObject
share many traits, SwiftUI manages their life cycles differently. To guarantee consistent outcomes when the current view generates the observed object, use the state object property wrapper. You can use the @ObservedObject
whenever you inject an observed object as a dependency.If the problem happens only after the app goes background, I think it will help if you revoke the listener and add another listener when the app goes to the foreground again, something like this:
Listen to app state changes in the view and delegate to the view model:
Then in the view model re-listen to the changes again: