skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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()
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search