Home > OS >  Firestore not returning data to @Published variable
Firestore not returning data to @Published variable

Time:01-11

I have a call to Firestore that grabs some data and stores to

@published var userJournals

In another view, I go to call the variable and it's empty. I checked the data in the initial pull from firestore and the data shows and is mapped successfully. Wondering what I'm doing wrong on the other view.

View 1

class JournalDashLogic: ObservableObject {
  @Published var userJournals = [UserJournalEntry]()
  @Published var userJournalIDs = [String]()

  func grabUserJournals(journalID: String) {
    //grab current user
    guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
      return
    }
    FirebaseManager.shared.firestore
      .collection("users")
      .document(uid)
      .collection("userJournalEntrys")
      .document(journalID)
      .collection("items")
      .addSnapshotListener { (snapshot, err) in
        guard let documents = snapshot?.documents else {
          print("no documents present")
          return
        }
        //map to journal entry struct
        self.userJournals = documents.map { (querySnapshot) -> UserJournalEntry in
          let data = querySnapshot.data()
          let dateCreated = data["dateCreated"] as? String ?? ""
          let dayOfWeek = data["dayOfWeek"] as? String ?? ""
          let mealCalories = data["mealCalories"] as? Int ?? 0
          let mealCarbs = data["mealCarbs"] as? Int ?? 0
          let mealFat = data["mealFat"] as? Int ?? 0
          let mealName = data["mealName"] as? String ?? ""
          let mealProtein = data["mealProtein"] as? Int ?? 0
          let MealServing = data["MealServing"] as? Int ?? 0
          let mealSaved = data["saved"] as? Bool ?? false
          let mealTiming = data["timeCreated"] as? String ?? ""

          let entry = UserJournalEntry(
            id: UUID().uuidString, mealName: mealName, mealFat: mealFat, mealCarbs: mealCarbs,
            mealProtein: mealProtein, mealCalories: mealCalories, MealServing: MealServing,
            mealSaved: mealSaved, mealTiming: mealTiming, dayOfWeek: dayOfWeek,
            totalCalories: "100", dateCreated: dateCreated)

          return entry

        }

      }
  }
}

View 2

.onAppear {

  for id in jm.userJournalIDs {
    jm.grabUserJournals(journalID: id)
  }

}

sheet presented from View 2

.sheet(isPresented: $showAllJournals) {
  SavedJournalsList(savedJournalIDs: jm.userJournalIDs, savedJournals: jm.userJournals)
    .transition(transition)

}

View 3

struct SavedJournalsList: View {
  @State var savedJournalIDs: [String]
  @State var savedJournals: [UserJournalEntry]

  var body: some View {
    VStack(alignment: .leading) {
      ForEach(savedJournals, id: \.self) { entry in

        HStack {
          Text(entry.dateCreated).bold()
          Text("Total Calories: 3200")
            .padding(.leading, 15)
        }
        .frame(maxWidth: .infinity)
        .multilineTextAlignment(.center)
        HStack {
          Text("200 Carbs")
            .foregroundColor(.gray)
          Text("250 Protein")
            .foregroundColor(.gray)
          Text("100 Fat")
            .foregroundColor(.gray)
        }
        .padding(.all, 5)
        .frame(maxWidth: .infinity)

      }

      .frame(width: 300, height: 100)
      .background(.white)
      .cornerRadius(15)
      .shadow(color: Color("LighterGray"), radius: 5, x: 0, y: 8)

    }

  }
}

CodePudding user response:

You can try this function which i have created for my project

func lisentDocument<T:Codable>(docPath: String, model: T.Type, completion:@escaping(T?,String?) -> Void) -> ListenerRegistration? {
        let docRef = db.document(docPath)
        var listner: ListenerRegistration? = nil
        listner = docRef.addSnapshotListener { response, error in
            if error != nil {
                completion(nil, error?.localizedDescription)
                print(listner)
            }
            do {
                let data = try response?.data(as: model)
                completion(data, nil)
            } catch {
                completion(nil, FBError.someThingWentWrong.message)
            }
        }
        
        return listner
    }

How to use

lisentDocument(docPath: "Doc Path", model: [UserJournalEntry].self,
                                          completion: { data, error in
        self.userJournals = data
    })

If you have Object as an response use can use model as UserJournalEntry.self

CodePudding user response:

A couple of notes:

  1. MealServing is in uppercase, I think it should probably be lowercase: mealServing.

  2. No need to map data manually, use Codable instead. See the official docs

  3. you're using a view model, so no need to use @State properties on your vies. Use the view models instead.

  4. You're iterating over the IDs in your view model and it seems like you're trying to set up a snapshot listener for each of those documents. This is inefficient, and you should instead set up a listener on the collection (or a query), and then assign the result of this to the published property userJournals. The documentation has an example that shows how to do this here. If you're interested in the code sample, check out this repo

  • Related