Search code examples
swiftfirebasegoogle-cloud-firestoreswiftuiidentifiable

How do I overwrite an identifiable object by refrencing it with an ID in swiftui?


I've simplified my code down so it's easier to see what i'm trying to do, I am building a feed of ratings that are sourced from firebase documents of whomever I am "following"

All that I want to do is whenever firebase says there is a new update to one of my reviews, change that Identifiable, as apposed to having to re-load every review from every person i'm following.

Here's my view:

import SwiftUI
import Firebase

struct Rating: Identifiable {
    var id: String
    var review: String
    var likes: Int
}

struct Home: View {
    
    @ObservedObject var feedViewModel = FeedViewModel()

    var body: some View {
        VStack{
                         
           ForEach(self.feedViewModel.allRatings, id: \.id){ rating in
               Text(rating.review)
               Text("\(rating.likes)")
           }
        }
     }
}

Here are my functions for FeedViewModel:

class FeedViewModel: ObservableObject {

     @Published var following = ["qedXpEcaRLhIa6zWjfJC", "1nDyDT4bIa7LBEaYGjHG", "4c9ZSPTQm2zZqztNlVUp", "YlvnziMdW8VfibEyCUws"]
     @Published var allRatings = [Rating]()

     init() {
         for doc in following {
              
              // For each person (aka doc) I am following we need to load all of it's ratings and connect the listeners
              getRatings(doc: doc)
              initializeListener(doc: doc)

          }
      }


    func getRatings(doc: String) {
        
        db.collection("ratings").document(doc).collection("public").getDocuments() { (querySnapshot, err) in
            
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                
 
                // We have to use += because we are loading lots of different documents into the allRatings array, allRatings turns into our feed of reviews.
                self.allRatings += querySnapshot!.documents.map { queryDocumentSnapshot -> Rating in
                   let data = queryDocumentSnapshot.data()
                   let id = ("\(doc)-\(queryDocumentSnapshot.documentID)")
                   let review = data["review"] as? String ?? ""
                   let likes = data["likes"] as? Int ?? 0

                   return Rating(id: id, review: review, likes: likes)

                }
            }
      }


    func initializeListener(doc: String){
        
        db.collection("ratings").document(doc).collection("public")
            .addSnapshotListener { (querySnapshot, error) in
            
            guard let snapshot = querySnapshot else {
                print("Error listening for channel updates")
                return
            }

            snapshot.documentChanges.forEach { change in

                for document in snapshot.documents{

                   let data = document.data()
                   let id = ("\(doc)-\(document.documentID)")
                   let review = data["review"] as? String ?? ""
                   let likes = data["likes"] as? Int ?? 0

                   // I thought swiftui Identifiables would be smart enough to not add another identifiable if there is already an existing one with the same id, but I was wrong, this just duplicates the rating
                   // self.allRatings.append(Rating(id: id, review: review, likes: likes))

                   // This is my current solution, to overwrite the Rating in the allRatings array, however I can not figure out how to get the index of the changed rating
                   // "0" should be replaced with whatever Rating index is being changed
                   self.allRatings[0] = Rating(id: id, review: review, likes: likes)

                }
            }
        }
     }

}

All I want is to make the "likes" of each rating live and update whenever someone likes a rating. It seems simple but I am relatively new to swiftui so I might be completely off on how I am doing this. Any help is greatly appreciated!


Solution

  • After a week of trying to figure this out I finally got it, hopefully this helps somebody, if you want to overwrite an identifiable in your array find the index by using your id. In my case my ID is the firebaseCollectionID + firebaseDocumentID, so this makes it always 100% unique AND it allows me to reference it from a snapshot listener...

    let index = self.allRatings.firstIndex { (Rating) -> Bool in
        Rating.id == id
    }
    

    Then overwrite it by doing:

    self.allRatings[index!] = Rating(id: id, review: review, likes: likes)