2

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?

4
  • Hi @ccmsd18, was my answer helpful ? Commented Jul 12, 2022 at 18:09
  • If this or any answer has solved your question please consider accepting it by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. Another option is to upvote the answer if you feel it is useful for you There is no obligation to do this. Commented Jul 12, 2022 at 18:52
  • Unfortunately not. I'm not using ObservedObject though, I am using @EnvironmentObject. I have updated the code above so you can see. So I'm not sure where you want me to use the @StateObject? Commented Jul 14, 2022 at 16:27
  • I just left a comment in my answer posted, hope it could answer your question. Also, let us know if the answer posted by @narek.sv helps you. Commented Aug 4, 2022 at 18:12

2 Answers 2

0

Reviewing the main issue you are getting, it seems that instead of using ObservedObject you should use StateObject.

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Use StateObject as opposed to ObservedObject when initializing the view. StateObject will prevent the object from being "re-initialized" when the view is modified or re-rendered. These excellent sources might help you: What is StateObject? and ObservedObject vs StateObject vs EnvironmentObject
0

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:

struct ViewExample: View { @Environment(\.scenePhase) var scenePhase @EnvironmentObject var userDetails: FirebaseRealTime @State var users: [Users] = [] var body: some View { VStack { ScrollView { ForEach(users) { user in Text("") } } } .onChange(of: scenePhase) { phase in if phase == .active { viewModel.reset() } } } } 

Then in the view model re-listen to the changes again:

final class FirebaseRealTime: ObservableObject { private let db = Firestore.firestore() private var listener: FirebaseFirestore.ListenerRegistration? @Published var myUsers = [Users]() func listenToUserCollection() { listener = db.collection("users").addSnapshotListener { (querySnapshot, error) in // ... } } func reset() { listener?.remove() listenToUserCollection() } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.