(Swift)Tell me the reason why I cannot query the items - ios

var menuArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchData {
self.tableView.reloadData()
print(self.menuArray)
}
}
func fetchData(completion: #escaping () -> Void){
let user = Auth.auth().currentUser
let saveDocument = Firestore.firestore()
let uploadDocument = saveDocument.collection("Posts")
let query = uploadDocument.whereField("LikeId", isEqualTo: user!.uid)
query.getDocuments { (querySnapshot, error) in
if error != nil {
print("error")
} else {
for document in querySnapshot!.documents {
if let menuValue = document.data()["Menu"] as? String{
self.menuArray.append(menuValue)
print(menuValue)
print(self.menuArray)
}}}}
completion()
I don't know why print Items came out [ ](nil)
Maybe query part are wrong,...
I use whereField to find User!.uid
I don't know the reason why this code are wrong.
please tell my the reason of this.

LikeId is an array, you need to do the following to check if the array contains a value or not:
let query = uploadDocument.whereField("LikeId", arrayContains: user!.uid)
https://firebase.google.com/docs/firestore/query-data/queries#array_membership

Related

How do I Display First Name of User Once They have logged in?

I am using this code to retrieve the first name from my Firebase database and trying to display it in a label but it always returns the "Document does not exist in cache". My firebase security is set to read and write true. Do you know what I am doing wrong?
func nameGreeting() -> String{
let db = Firestore.firestore()
let userId = Auth.auth().currentUser!.uid;(Auth.auth().currentUser!.uid);
let docRef = db.collection("users").document(userId)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let name = document.get("firstname")
print("Cached document data: \(String(describing: name))")
} else {
print("Document does not exist in cache")
}
}
return ""
}
you are using an async function (docRef.getDocument...) inside "nameGreeting", so you are returning "" before the firebase function is finished. Try something like this:
func nameGreeting(completion: #escaping ((String?) -> Void)) {
let db = Firestore.firestore()
let userId = Auth.auth().currentUser!.uid
let docRef = db.collection("users").document(userId)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let name = document.get("firstname")
print("Cached document data: \(String(describing: name))")
completion(name)
} else {
print("Document does not exist in cache")
completion(nil)
}
}
}
and call it like this:
nameGreeting() { userName in
print("----> userName: \(userName)")
}

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.

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
}

How to create array in array using collections and sub collections in Firestore?

I need to create an array of Categories that contains Questions array.
struct CategoryFB {
var title: String
var id: Int
var questions: [QuestionsFB]
var dictionary: [String : Any] {
return ["title" : title,
"id" : id]
}
}
extension CategoryFB {
init?(dictionary: [String : Any], questions: [QuestionsFB]) {
guard let title = dictionary["title"] as? String, let id = dictionary["id"] as? Int else { return nil }
self.init(title: title, id: id, questions: questions)
}
}
Firestore has a following structure
Collection("Categories")
Document("some_id")
Collection("Questions")
How to create array like this?
array = [Category(title: "First",
questions: [
Question("1"),
...
]),
... ]
My try was wrong:
db.collection("Categories").order(by: "id", descending: false).getDocuments {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
for document in querySnapshot!.documents {
print(document.documentID)
self.db.collection("Categories").document(document.documentID).collection("Questions").getDocuments(completion: { (subQuerySnapshot, error) in
if error != nil {
print(error!.localizedDescription)
} else {
var questionsArray: [QuestionsFB]?
questionsArray = subQuerySnapshot?.documents.compactMap({QuestionsFB(dictionary: $0.data())})
self.categoriesArray = querySnapshot?.documents.compactMap({CategoryFB(dictionary: $0.data(), questions: questionsArray!)})
print(self.categoriesArray![0].questions.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
}
Your main problem seems to stem from the fact that you're regenerating your categories array every time you run your subquery, and when you do, you're only supplying a single questions array to the entire thing.
There's lots of ways to fix this. I would probably break it up so that you a) First allow yourself to create a category array without any questions, and then b) Go back through each of your individual subQueries and insert them into your categories as you get them.
Your final code might look something like this. Note that this would mean changing your Category object so that you can first create it without a Questions array, and implementing this custom addQuestions:toCategory: method (which would be a whole lot easier if you stored your categories as a dictionary instead of an array)
db.collection("Categories").order(by: "id", descending: false).getDocuments {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
self.categoriesArray = querySnapshot?.documents.compactMap({CategoryFB(dictionary: $0.data()})
for document in querySnapshot!.documents {
print(document.documentID)
self.db.collection("Categories").document(document.documentID).collection("Questions").getDocuments(completion: { (subQuerySnapshot, error) in
if error != nil {
print(error!.localizedDescription)
} else {
var questionsArray: [QuestionsFB]?
questionsArray = subQuerySnapshot?.documents.compactMap({QuestionsFB(dictionary: $0.data())})
self.addQuestions(questionsArray toCategory: document.documentID)
print(self.categoriesArray![0].questions.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
}
Alternately, if you think you're going to be in a situation where you're always going to want to grab your questions every time you want to grab a category, you might consider not putting them in a subcollection at all, and just making them a map in the original category document.
This is the solution which I found by myself. Hopefully this will help someone in the future.
func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = db.collection("Categories")
var data = [Any]()
rootCollection.order(by: "id", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
for category in topSnapshot {
rootCollection.document(category.documentID).collection("Questions").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var questions = [Question]()
for document in snapshot {
let title = document.data()["title"] as! String
let details = document.data()["details"] as! String
let article = document.data()["article"] as! String
let link = document.data()["link"] as! String
let id = document.data()["id"] as! String
let possibleAnswers = document.data()["possibleAnswers"] as! [String]
let rightAnswerID = document.data()["rightAnswerID"] as! Int
let newQuestion = Question(title: title, article: article, details: details, link: link, possibleAnswers: possibleAnswers, rightAnswerID: rightAnswerID, id: id)
questions.append(newQuestion)
}
let categoryTitle = category.data()["title"] as! String
let collectionID = category.data()["id"] as! Int
let newCategory = Category(title: categoryTitle, id: collectionID, questions: questions)
data.append(newCategory)
//Return data on completion
completion(data)
})
}
}
})
}

Swift: Saving Firebase data to CoreData in async operation

I am attempting to pull data from Firebase and then save it to CoreData but am having trouble with the async operation. I have a custom function that returns [ConversationStruct] upon completion. I then do a forEach to save it to CoreData.
However, my current implementation saves the object multiple times, ie Firebase have 10 entries, but CoreData would somehow give me 40 over entries which most are repeated. I suspect the problem is in my completionHandler.
//At ViewDidLoad of my VC when I pull the conversations from Firebase
FirebaseClient.shared.getConversationsForCoreData(userUID) { (results, error) in
if let error = error {
print(error)
} else if let results = results {
print(results.count)
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
results.forEach({ (c) in
let conversation = Conversation(context: privateContext)
conversation.conversationStartTime = c.conversationStartTime
conversation.recipientID = c.recipientID
conversation.shoutoutID = c.shoutoutID
conversation.unreadMessagesCount = Int32(c.unreadMessagesCount!)
conversation.profileImage = c.profileImage
conversation.recipientUsername = c.recipientUsername
})
do {
try privateContext.save()
} catch let error {
print(error)
}
}
}
//At FirebaseClient
func getConversationsForCoreData(_ userUID: String, _ completionHandler: #escaping (_ conversations: [ConversationStruct]?, _ error: Error?) -> Void) {
var conversations = [ConversationStruct]()
ref.child("conversations").child(userUID).observeSingleEvent(of: .value) { (snapshot) in
for snap in snapshot.children {
let snapDatasnapshot = snap as! DataSnapshot
let snapValues = snapDatasnapshot.value as! [String: AnyObject]
let recipientUID = snapDatasnapshot.key
for (key, value) in snapValues {
//Some other logic
self.getUserInfo(recipientUID, { (results, error) in
if let error = error {
print(error.localizedDescription)
} else if let results = results {
let username = results["username"] as! String
let profileImageUrl = results["profileImageUrl"] as! String
URLClient.shared.getImageData(profileImageUrl, { (data, error) in
if let error = error {
print(error.localizedDescription)
} else if let imageData = data {
let convo = ConversationStruct(conversationStartTime: conversationStartTime, shoutoutID: shoutoutID, recipientID: shoutoutID, unreadMessagesCount: unreadMessagesCount, recipientUsername: username, profileImage: imageData)
conversations.append(convo)
}
completionHandler(conversations, nil)
})
}
})
}
}
}
}
struct ConversationStruct {
var conversationStartTime: Double
var shoutoutID: String
var recipientID: String
var unreadMessagesCount: Int?
var recipientUsername: String?
var profileImage: Data?
}
The print statement would print the count as and when the operation completes. This seems to tell me that privateContext is saving the entities when the results are consistently being downloaded which resulted in 40 over entries. Would anyone be able to point me out in the right direction how to resolve this?
Also, the implementation does not persist.

Resources