Memphis Meng
Memphis Meng

Reputation: 1671

Retrieve and Update data stored in Firestore

I am developing a mobile app on iOS and need to track some data when a user clicks a button. However, nothing is shown when I try to get the data according to the official doc. Here is my snippet:

import SwiftUI
import FirebaseStorage
import FirebaseCore
import FirebaseFirestore

struct Station_View: View {
    @State private var showingAlert = false
    var ref: Firestore!
    
    var station_ : station
    var food : [food] = []
    var body: some View {
        VStack(alignment: .leading, spacing: 10, content: {
            VStack {
                ForEach(station_.menu_items, id: \.self) { i in
                    Divider()
                    .frame(width: 400, height: 1)
                    .background(Color("Black"))
                    .padding(.vertical,0)
                    HStack {
                        VStack (alignment: .leading) {
                            Text(i.name + ", " + i.calories + "cal, protein: " + i.protein)
                            .font(.headline)
                            .foregroundColor(Color("Black"))
                                
                        }.padding(.leading, 8)
                        Spacer()
                        if (Int(i.protein)! > 10) {
                            Button(action: {
//                                print("Button action")
////////// I retrieved data here //////////////////                      
                                let docRef = ref?.collection("users").document("7lqIqxc7SGPrbRhhQWZ0rdNuKnb2")
                                docRef?.getDocument { (document, error) in
                                    if let document = document, document.exists {
                                        let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
                                        print("Document data: \(dataDescription)")
                                    } else {
                                        print("Document does not exist")
                                    }
                                }
////////// I retrieved data here //////////////////  
                                self.showingAlert = true
                            }) {
                                HStack {
                                    Image(systemName: "p.circle")
                                    Text("+50xp")
                                }.padding(10.0)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 6.0)
                                        .stroke(lineWidth: 2.0)
                                )
                            }
                            .alert(isPresented: $showingAlert) {
                            () -> Alert in
                            Alert(title: Text("Congratulations!"), message: Text("You had a protein meal, XP+50!"), dismissButton: .default(Text("OK")))
                                }
                        }
                        if (i.is_vegan) {
                            Button(action: {
//                                print("Button action")
////////// I retrieved data here //////////////////  
                                let docRef = ref?.collection("users").document("7lqIqxc7SGPrbRhhQWZ0rdNuKnb2")
                                docRef?.getDocument { (document, error) in
                                    if let document = document, document.exists {
                                        let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
                                        print("Document data: \(dataDescription)")
                                    } else {
                                        print("Document does not exist")
                                    }
                                }
////////// I retrieved data here //////////////////  
                                self.showingAlert = true
                            }) {
                                HStack {
                                    Image(systemName: "leaf")
                                    Text("+50xp")
                                }.padding(10.0)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 6.0)
                                        .stroke(lineWidth: 2.0)
                                )
                            }
                            .alert(isPresented: $showingAlert) {
                                () -> Alert in
                                Alert(title: Text("Congratulations!"), message: Text("You had a vegan meal, XP+50!"), dismissButton: .default(Text("OK")))
                                    }
                        }
                            
                       
                    }
                    .padding(.init(top: 12, leading: 0, bottom: 12, trailing: 0))
                    
                    
                    
                }
            }
        } )
    }
}

What can I do to make it come true? I am expecting to update only one key-value pair while the others remain the same when the data is collected back.

Upvotes: 0

Views: 288

Answers (1)

xTwisteDx
xTwisteDx

Reputation: 2472

Firstly, when working with SwiftUI you should always use a ViewModel. This is a weird transition at first but it will make your code infinitely easier to understand and keep track of. Here's the basic structure.

View Model

class YourViewModel: ObservableObject {
    @Published var isTrue = false

    func isValueTrue(){
         print(isTrue.description)
    }
}

Notice that there are a few things going on here, the ObservableObject and @Published essentially this means that the object YourViewModel can be observed with published properties, or the ones that can be bound to a view. To use it in a view you can do this.

View

struct YourView: View {
    //This is your ViewModel reference.
    //Use is to bind all your details to the view with
    //something like yourViewModel.firstName or $yourViewModel.firstName
    @observedObject var yourViewModel = YourViewModel()
    var body: some View {
        Button("Change Bool") {
             yourViewModel.isTrue.toggle()
             yourViewModel.isValueTrue()
        }
    }
}

This is the basic structure for an MVVM pattern and will save you tons of space in your view, making it much much easier to read and maintain. Typically you'll have a separate .swift file for the View and for the ViewModel try not to combine them, and abstract as much as you can.

To answer the ultimate question, how do you retrieve data from Firebase and update that same data? Well, the answer is as follows, I will demonstrate using a function and a property within a ViewModel that you can Bind to your views to update them.

Getting Firebase Data

//These properties are a part of the VIEWMODEL and can be bound to the view
//Using yourViewModel.someProperty
@Published var firstName = ""
@Published var lastName = ""
@Published var email = ""

func fetchFirebaseData() {
            guard let uid = Auth.auth().currentUser?.uid else {
            print("Handle Error")
            return
        }

        //Create Database Reference
        let db = Firestore.firestore()

        //Reference the collection and document. In this example
        //I'm accessing users/someUserId
        let fsUserProfile = db.collection("users").document(uid)
        
        //Request the document 
        fsUserProfile.getDocument { (snapshot, err) in
            if err != nil { return }
            self.fetchImageFromURL(url: URL(string: snapshot?.get("profile_image_url") as? String ?? "")!)
            self.firstName = snapshot?.get("first_name") as? String ?? ""
            self.lastName = snapshot?.get("last_name") as? String ?? ""
            self.email = snapshot?.get("email") as? String ?? ""
        }
}

Updating Firebase Data

This is a simple way of updating your firebase data. This is handled by passing a dictionary with a key which is the field and a value associated with it. WARNING: DO NOT use setData(...) it will clear everything else that you had in there. setData(...) is useful for first time data creation such as registering an account, creating a new entry, etc..

func updateFirebaseData(firstName: String) {
    if let user = Auth.auth().currentUser {
         let db = Firestore.firestore()   
         db.collection("users").document(user.uid).updateData(["first_name": firstName])
        }
    }

Usage

struct YourView: View {
    @observedObject var yourViewModel = YourViewModel()
    
    var body: some View {
        
        VStack {
            
            //Fetching Example
            VStack {
                Button("Fetch Data") {
                    yourViewModel.fetchFirebaseData()
                }
                Text(yourViewModel.firstName)
            }
            
            //Setting Example
            VStack {
                Button("Update Data") {
                    //You could have this "John" value, a property
                    //of your ViewModel as well, or a text input, or whatever
                    //you want.
                    yourViewModel.updateFirebaseData(firstName: "John")
                }
            }
        }
    }
}

Notice how much cleaner the MVVM structure is when working in SwiftUI, once you do it for a few days, it will become second nature.

Upvotes: 1

Related Questions