I'm new in swift and SwiftUI and i have a big problem, I hope someone can help me. In fact, I have a function were I use a call to firebase Db, but the function end before the response of firebase. So is there any way to do like an await in swift ? I try to find by myself but everything I try doesn't work.
I put a sample of code, it's maybe going to be clearer.
extension SessionStore {
func checkReferralCode(){
let docRef = db.document(user.referredBy)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let otherUser = self.changeReferralUserInformation(dataDescription: document)
docRef.setData(otherUser)
self.user.moneyBalance = 1
return
} else {
print("No referral Code")
self.firestoreError = "referralCode_unvalid"
self.user.referredBy = ""
return
}
}
}
func doInscriptionInformation() {
if (self.user.referredBy != "") {
self.checkReferralCode()
if (self.firestoreError == "" ) {
/* it's always go in this way but 1 secs after the firestoreError change */
print("START UPLOAD")
self.determineUploadType()
} else {
return
}
}
}
The output it's gonna be :
$> START UPLOAD
$> No referral Code
I found an answer using semaphore, here the link of the tutorial https://medium.com/#roykronenfeld/semaphores-in-swift-e296ea80f860
Here the code
func checkReferralCode(semaphore: DispatchSemaphore){
let docRef = self.db.document(self.user.referredBy)
semaphore.wait()
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let otherUser = self.changeReferralUserInformation(dataDescription: document)
docRef.setData(otherUser)
self.user.moneyBalance = 1
semaphore.signal()
} else {
self.firestoreError = "referralCode_unvalid"
self.user.referredBy = ""
semaphore.signal()
}
}
}
func doInscriptionInformation() {
let semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global(qos: .userInteractive).async {
if (self.user.referredBy != "") {
self.checkReferralCode(semaphore: semaphore)
semaphore.wait()
if (self.firestoreError == "" ) {
self.determineUploadType()
}
else {
print("No good referral code")
}
semaphore.signal()
}
}
}
Hope that can help someone
make docRef a variable of the class instead of a local variable like so:
class WhateverClassNameYouHave: InheritedClass {
let docRef : DocRef (whatever type it is)
func checkCode() {
self.docRef = /* get firebase doc */
/* Call to firebase function */
docRef.getDocument { (document, error) in
/* Do my stuff*/
} else {
/*change variable to error*/
self.firestoreError = "referralCode_unvalid"
}
}
/*But the fonction end before the response of firebase so my self.firestoreError isn't update */
}
Related
func fetchUser() {
guard let uid = userSession?.uid else { return }
Firestore.firestore().collection("users").document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: User.self) else { return }
self.currentUser = user
}
}
The error is thrown on the "guard let user" line. Any idea on how to fix this error? Also, I am not using Cocoapods ... I am using Firebase from the github sdk
You need to ensure that you have:
import FirebaseFirestoreSwift
With this the data(as: ) function, it should work.
it seems you are missing the "documents" step, could you try something like this:
func fetchUser() {
guard let uid = userSession?.uid else { return }
Firestore.firestore().collection("users").document(uid).getDocument { snapshot, _ in
guard let docs = snapshot else { return }
for doc in docs.documents {
let user = doc.data(as: User.self)
self.currentUser = user
// break as required or just get the first in documents
}
}
}
Right now I have to call the function (calculatePortfolioGrossBalance) 3 times for the value to update, what am I doing wrong in the state logic?
In the code below, when I call in an init the function calculatePortfolioGrossBalance() it returns empty [], I have to call it 3 times for the value to update, However... if I print the values of getTokenBalancesModel in the line DispatchQueue.main.async { I can see the values are there, so how come in calculatePortfolioGrossBalance are not?
final class TokenBalancesClassAViewModel: ObservableObject {
#Published var getTokenBalancesModel: [TokenBalancesItemsModel] = [TokenBalancesItemsModel]()
#Published var portfolioGrossBalance: String = "0.0"
func calculatePortfolioGrossBalance() {
getTokenBalances()
DispatchQueue.main.async {
var totalBalance: Double = 0
for item in self.getTokenBalancesModel {
totalBalance += Double(item.quote!)
}
self.portfolioGrossBalance = String(format:"%.2f", totalBalance)
print(self.portfolioGrossBalance)
}
}
func getTokenBalances() {
guard let url = URL(string: "someUrlHeidiGaveMe") else {
print("Invalid URL")
return
}
print("Calling getTokenBalances() ...")
AF.request(url, method: .get).validate().responseData(completionHandler: { data in
do {
guard let data = data.data else {
print("Response Error:", data.error as Any)
return
}
let apiJsonData = try JSONDecoder().decode(TokenBalancesModel.self, from: data)
DispatchQueue.main.async {
self.getTokenBalancesModel = apiJsonData.data.items
}
} catch {
print("ERROR:", error)
}
})
}
}
you need to read up on using asynchronous functions, how to set them up and how to use them. This is important. Try something like this (untested):
final class TokenBalancesClassAViewModel: ObservableObject {
#Published var getTokenBalancesModel: [TokenBalancesItemsModel] = [TokenBalancesItemsModel]()
#Published var portfolioGrossBalance: String = "0.0"
func calculatePortfolioGrossBalance() {
getTokenBalances() { isGood in
if isGood {
var totalBalance: Double = 0
for item in self.getTokenBalancesModel {
totalBalance += Double(item.quote!)
}
self.portfolioGrossBalance = String(format:"%.2f", totalBalance)
print(self.portfolioGrossBalance)
}
}
}
func getTokenBalances(completion: #escaping (Bool) -> Void) {
guard let url = URL(string: "someUrlHeidiGaveMe") else {
print("Invalid URL")
completion(false)
return
}
print("Calling getTokenBalances() ...")
AF.request(url, method: .get).validate().responseData(completionHandler: { data in
do {
guard let data = data.data else {
print("Response Error:", data.error as Any)
completion(false)
return
}
let apiJsonData = try JSONDecoder().decode(TokenBalancesModel.self, from: data)
DispatchQueue.main.async {
self.getTokenBalancesModel = apiJsonData.data.items
completion(true)
}
} catch {
print("ERROR:", error)
completion(false)
}
})
}
}
Seems there's no way to use boolean to return the checking result is exist or not. it only works in printing out the result...
func checkSeatAvailable() -> Bool{
var Exist: Bool
let g = DispatchGroup()
let resDate = ResDateTxt.text
let db = Firestore.firestore()
let docRef = db.collection("Reservations").whereField("resDate", isEqualTo: resDate!)
g.enter()
docRef.getDocuments() { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
print("Document does not exist")
Exist = true
g.leave()
} else {
print("Document data: \(snapshot) ")
Exist = false
g.leave()
}
}
}
g.notify(queue:.main) {}
print("\(Exist)")
return Exist
}
Add a completion Handler in your function as getDocuments function is asynchronous and return immediately. You can modify it like so.
func checkSeatAvailable(Completion:#escaping((Bool)->())){
//let g = DispatchGroup()
let resDate = ResDateTxt.text
let db = Firestore.firestore()
let docRef = db.collection("Reservations").whereField("resDate", isEqualTo: resDate!)
//g.enter()
docRef.getDocuments() { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
print("Document does not exist")
//g.leave()
} else {
print("Document data: \(snapshot) ")
Completion(true)
//g.leave()
}
}
}
//g.notify(queue:.main) {}
//print("\(Exist)")
//return Exist
}
And then call that function like that;
checkSeatAvailable { (boolValue) in
print(boolValue)
}
I want to use 2 Strings that I get with those two "loops" from firebase and use them in another "loop" to upload them with a bunch of other Information.
My problem is, that I somehow can't get the values of fullname and pfp that I downloaded, into the upload to firebase.
Any ideas on how to solve this issue?
func sendToFire(){
let combined = "\(userID)" + "\(number)"
let docRef = db.collection("posts").document(combined)
let description = self.textPost.text
let nameRef = db.collection("users").document(userID)
var fullname = ""
var pfp = ""
if fireImage == nil {
nameRef.getDocument { (document, error) in
if let document = document{
fullname = document.get("fullname") as! String
}else{
print("Coulnt get fullname")
}
}
nameRef.getDocument { (document, error) in
if let document = document{
pfp = document.get("profileimage") as! String
}else{
print("Couldn't get profileimage")
}
}
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("Post ID already taken")
} else {
print("Post Document gets created")
self.db.collection("posts").document(combined).setData([
"description": description!,
"likes": self.likes,
"postType": 0,
"profileImage": pfp,
"time": self.date,
"uid": self.userID,
"username": fullname
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Post Document successfully written!")
}
}
}
}
}
}
Add document.exists in the if let
nameRef.getDocument { (document, error) in
if let document = document, document.exists{
fullname = document.get("fullname") as! String
}else{
print("Coulnt get fullname")
}
}
nameRef.getDocument { (document, error) in
if let document = document, document.exists{
pfp = document.get("profileimage") as! String
}else{
print("Couldn't get profileimage")
}
}
Check the actual key names in the response fullname and profileimage.
I would like to fetch the user/author of the article into a computed property. But this never happens, below code would print 1, 2, 4.
struct Article {
private let database = Firestore.firestore()
var authorId: String
var message: String
var author: User? {
print(1)
var fetchedAuthor: User?
let semaphore = DispatchSemaphore(value: 0)
print(2)
database.collection("users").document(authorId).getDocument { (document, error) in
print(3)
guard let user = document.flatMap({User(dictionary: $0.data())}) else {
print("Could not get author: \(error.debugDescription)")
semaphore.signal()
return
}
fetchedAuthor = user
semaphore.signal()
}
print(4)
semaphore.wait()
print(5)
return fetchedAuthor
}
}
Adding pseudo/ sample code to the comment by #Connor, since I just went through this.
extension User {
static func getBy(documentID id: String, completion: #escaping (User)->()) {
database.collection("users").document(id).getDocument { (document, error) in
guard let user = document.flatMap({User(dictionary: $0.data())}) else {
print("Could not get author: \(error.debugDescription)")
return
}
completion(user)
}
}
Then in your Article struct:
struct Article {
....
init(){
....
// Get the user
User.getBy(documentID: self.userID) { user in
self.author = user
}
}
}
semaphore.wait() //is for requesting the resource(semaphore)
semaphore.singal() //is for releasing the resource(semaphore)
Change code as per below.
var author: User? {
print(1)
var fetchedAuthor: User?
let semaphore = DispatchSemaphore(value: 1)
print(2)
database.collection("users").document(authorId).getDocument { (document, error) in
semaphore.wait()
print(3)
guard let user = document.flatMap({User(dictionary: $0.data())}) else {
print("Could not get author: \(error.debugDescription)")
return
}
fetchedAuthor = user
semaphore.signal()
}
print(4)
print(5)
return fetchedAuthor
}