Returning name from Firestore? - ios

I'm trying to return a name after getting it on Firestore, but for some reason it's not working.
Here's my code:
func getName() -> String {
var name = ""
db.collection("users").whereField("email", isEqualTo: user.email!).getDocuments { (snapshot, error) in
if error != nil {
print(error!)
} else {
for document in (snapshot?.documents)! {
name = document.data()["name"] as! String
// if I add `print(name) here, it works.`
}
}
}
return name
}
But it returns an empty string :/ I want to return the actual name. How do I fix this?

getDocuments is an asynchronous function. This means the name variable doesn't wait for the function to complete before continue executing. If you want to return the returned name from the document, you can take a look at the following code:
func getName(_ completion: (String) -> ()) {
db.collection("users").whereField("email", isEqualTo: user.email!).getDocuments { (snapshot, error) in
if error != nil {
print(error!)
} else {
for document in (snapshot?.documents)! {
name = document.data()["name"] as! String
completion(name)
}
}
}
}
getName { name in
print(name)
}

Related

reading data from firestore and save locally in an array

I want to retrieve usernames from a users collection and save in an array. I use this:
var usernames:[String] = []
override func viewDidLoad() {
super.viewDidLoad(
populateUsernames()
}
func populateUsernames() {
let db = Firestore.firestore()
db.collection("users").getDocuments() { [self] (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let username = document.get("username") as! String
usernames.append(username)
print(usernames) //THIS PRINTS ["user1", "user2"] WHICH IS CORRECT
}
print(usernames) // THIS PRINTS [] WHICH IS FALSE
}
}
}
Why does the array reset to [] after the for loop?
There is nothing in your code that would cause this behavior. You're either printing the wrong array or something else is overwriting it, which doesn't seem likely. I notice that you aren't referring to the array with self which you would need to do in this closure. Therefore, rename the array for testing purposes.
var usernames2: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
populateUsernames()
}
func populateUsernames() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot {
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
self.usernames2.append(username)
print(username)
} else {
print("username not found")
}
}
print(self.usernames2)
} else {
if let error = error {
print(error)
}
}
}
}
You also crudely parse these documents which may not be harmful but is nonetheless unsafe, which this code addresses.

Firebase for SWIFT iOS: returning nil value in function [duplicate]

This question already has an answer here:
Why function return nil FireBase Swift [duplicate]
(1 answer)
Closed 2 years ago.
I am trying to access my firestore database which store the userid, username in the collection called user, I want to return the username of the current user, however when I try to run the below function, it is returning me nil value.... do you know anything that I'm missing? A completion handler?
func display(userid: String) -> String
{
var displayname: String
let docRef = db.collection("user").document(uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
displayname = document.get("username") as! String
} else {
print("Document does not exist")
}
}
return displayname
}
Then I tried to change to this but it still doesn't change my variable. It seems like it's trapped inside my function
var name = "placeholder"
func display(userid: String, completion: #escaping (String) -> Void) {
let docRef = db.collection("user").document(userid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
if let displayName = document.get("username") as? String {
self.name = displayName
completion(displayName)
}
} else {
print("Document does not exist")
}
}
}
func create()
{
display(userid: uid){
[weak self] displayName in
print(displayName)
self!.name = displayName
}
var ref: DocumentReference? = nil
ref = db.collection("Request").addDocument(data: ["requestername": "name"]
.....
}
You need to use closures as getDocument() does not return synchronously.
func display(userid: String, handler: (String) -> Void) {
let docRef = db.collection("user").document(uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
if let displayName = document.get("username") as? String {
handler(displayName)
}
} else {
print("Document does not exist")
}
}
}
And use it like so:
self.myLabel.text = "Loading..."
display(userid: {USER_ID}) { [weak self] displayName in
print(displayName)
self.myLabel.text = displayName
}

Firestore iOS - Ordering collection by field in document

I have an array called "homeList" which observers "CURRENT_USER_FRIENDS_REF" collection and places it in the array. How can I make it so I can order this array by the "timestamp" field found in the document snapshot.
homeList array function
var homeList = [User]()
func addHomeObserver(_ update: #escaping () -> Void) {
CURRENT_USER_FRIENDS_REF.getDocuments { snapshot, error in
self.homeList.removeAll()
guard error == nil else {
#if DEBUG
print("Error retrieving collection")
#endif
return
}
let group = DispatchGroup()
for document in snapshot!.documents {
let whosfrom = document.get("fromId") as? String
let id = document.documentID
**let timestamp = document.get("timestamp") as? NSNumber**
group.enter()
self.getUser(id, completion: { (user) in
if whosfrom != self.CURRENT_USER_ID {
self.homeList.append(user)
}
group.leave()
})
}
group.notify(queue: .main) {
update()
}
}
}
Current user friends reference:
var CURRENT_USER_FRIENDS_REF: CollectionReference {
return CURRENT_USER_REF.collection("friends")
}
Thanks.
You can use order(by on a collection reference to get the result.
CURRENT_USER_FRIENDS_REF.order(by: "timestamp", descending: true).getDocuments { snapshot, error in
}

Optional Still Returning Nil After Assigning Value

I am working on a similar feature to 'liking/unliking a post'.
I have an MVVM architecture as;
struct MyStructModel {
var isLiked: Bool? = false
}
class MyStructView {
var isLiked: Bool
init(myStructModel: MyStructModel) {
self.isLiked = myStructModel.isLiked ?? false
}
}
I successfully get the value of whether the post is liked or not here;
func isPostLiked(documentID: String, completion: #escaping (Bool) -> Void) {
guard let authID = auth.id else { return }
let query = reference(to: .users).document(authID).collection("liked").document(documentID)
query.getDocument { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let data = snapshot?.data() else { return }
if let value = data["isLiked"] as? Bool {
completion(value)
} else {
completion(false)
}
}
}
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let snapshotDocuments = snapshot?.documents else { return }
for document in snapshotDocuments {
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
// isLiked is nil here...
self.isPostLiked(documentID: post.documentID!) { (isLiked) in
post.isLiked = isLiked
print("MODEL SAYS: \(post.isLiked!)")
// isLiked is correct value here...
}
posts.append(post)
}
completion(posts)
}
}
}
However, when it gets to my cell the value is still nil.
Adding Cell Code:
var post: MyStructView? {
didSet {
guard let post = post else { return }
print(post.isLiked!)
}
}
Your isLiked property is likely nil in your cells because the retrieveReviews function doesn't wait for the isPostLiked function to complete before completing itself.
You could easily solve this issue by using DispatchGroups. This would allow you to make sure all of your Posts have their isLiked value properly set before being inserted in the array, and then simply use the DispatchGroup's notify block to return all the loaded posts via the completion handler:
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { [weak self] (snapshot, error) in
guard let self = self else { return }
if error != nil {
return
}
guard let documents = snapshot?.documents else { return }
let dispatchGroup = DispatchGroup()
for document in documents {
dispatchGroup.enter()
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
self.isPostLiked(documentID: post.documentID!) { isLiked in
post.isLiked = isLiked
posts.append(post)
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
completion(posts)
}
}
}

CKQueryOperation queryCompletionBlock return a nil cursor

I'm fetching a CloudKit database with CKQueryOperation. For some reason every time when I press a fetch button, the first time I get a nil cursor. The second time it fetches and gets data, it's all good. When I check the recordFetchedBlock it does get the results and appends them, but at the end the array is empty. I don't understand why this happens. I want to show the results immediately since they have been fetched. I think the problem is with the nil cursor, but I'm open for other suggestions. Here's my code:
public class CloudKitDatabase {
static let shared = CloudKitDatabase()
var records = [CKRecord]()
let publicData = CKContainer.default().publicCloudDatabase
init() {
self.fetchRecords()
}
func fetchRecords() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "OECD", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
func queryServer(_ cursor: CKQueryOperation.Cursor) {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
The Debug area tells me that:
CURSOR IS NIL
and CloudKitDatabase.shared.records.isEmpty is true
First try some configs on the first query;
let queryOperation = CKQueryOperation(query: query)
queryOperation.queuePriority = .veryHigh
queryOperation.resultsLimit = 99 // built in limit is 400
Next, don't do the cursor calls in a dispatch and include your completions;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
and;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
also don't forget to empty your records array at the beginning of fetchRecords otherwise successive calls will get the same records in the array.

Resources