Search code examples
iosfirebasegoogle-cloud-firestoreswiftui

View is emtpy after onAppear call to firebase firestore after button click (From another view)


I am trying to populate a view when a button is tapped on a different view. When I used my mock data, everything worked fine. However, when I switch to calling my firestore db data, nothing shows up. I'm still a few months into learning swiftui but I called on db the same way in a different part of my project which worked well. Here is an example of the code I am using.

import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct RewardsView: View {
    
// Dummy Data
//    var rewardItems: [Reward] = [
//        .init(RewardText: "10% Off Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 10),
//        .init(RewardText: "10% Off Extra Service", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 8),
//        .init(RewardText: "20% Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 15),
//        .init(RewardText: "30% off Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 20),
//        .init(RewardText: "25% Off Next Haircut", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 18),
//        .init(RewardText: "45% Off Next Haircut", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 30)
//    ]
    

    

    @EnvironmentObject private var rewardsViewModel: RewardViewModel
    @EnvironmentObject private var loyaltyViewModel: LoyaltyViewModel
    
    @State var rewards: [Reward] = []
    
    func getRewards() async throws -> [Reward]{
        let db = Firestore.firestore()
        let snapshot = try await db.collection("redeemRewards").getDocuments()
        let rewards = snapshot.documents.compactMap{try? $0.data(as: Reward.self)}
        
        return rewards
    }
   
    
    
    var body: some View {
        NavigationStack{
            ScrollView {
                LazyVGrid (columns: [
                    GridItem(.flexible()),
                    GridItem(.flexible()),
                ], spacing: 16, content:{
                    ForEach(rewardsViewModel.rewards, id: \.self) { reward in
                        RewardsCardView(rewardPhoto:reward.rewardPhoto, rewardText: reward.rewardText, rewardRedeemed: reward.rewardRedeemed, costOfReward: reward.costOfReward)
                            .environmentObject(rewardsViewModel)
                            .environmentObject(loyaltyViewModel)
                        
                    }
                })
            }
            .onAppear {
                Task{
                    do {
                        let newRewards = try await getRewards()
                        rewards.append(contentsOf: newRewards)
                    } catch {
                        print("Error Fetching rewards: \(error.localizedDescription)")
                    }
                }
            }
            .navigationTitle("Rewards")
        }
    }
}

//#Preview {
//    RewardsView()
//}

struct Reward: Identifiable, Codable,Hashable {
    @DocumentID var id: String?
    let rewardText: String
    let rewardRedeemed: Bool
    let rewardPhoto: String
    let costOfReward: CGFloat
    let timestamp: Timestamp
    let userId: String
    let remainingPoints: CGFloat
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: Reward, rhs: Reward) -> Bool {
        lhs.id == rhs.id
    }
}

Am I possibly misunderstanding how a view is constructed at runtime?

Thanks for the help, and consideration.


Solution

  • As @lorem ipsum commented, there would be many places that can be improved but the main reason your code doesn't work is that you're trying to display viewModel.rewards but the fetched data is stored in rewards in the view.

    So, try replace

    ForEach(rewardsViewModel.rewards, id: \.self) { reward in
    

    with

    ForEach(rewards, id: \.self) { reward in