Reputation: 1671
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
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.
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.
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.
//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 ?? ""
}
}
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])
}
}
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