I'm Calling this function to retrieve users from firestore:
Each time a user is modify I want to update the users array.
func fetchUsers( complete: #escaping ( _ success: Bool, _ users: [User], _ error: Error? )->()) {
//self.users = []
let circleId = UserDefaults.standard.string(forKey: "circleId") ?? ""
DataService.call.REF_CIRCLES.document(circleId).collection("insiders").order(by: "position", descending: false).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let data = diff.document.data()
let id = diff.document.documentID
let user = User(key: id, data: data)
self.users.append(user)
complete(true, self.users, nil)
}
if (diff.type == .modified) {
// Update users array if user data is modified
if !self.users.isEmpty {
self.users = []
let data = diff.document.data()
let id = diff.document.documentID
let user = User(key: id, data: data)
self.users.append(user)
complete(true, self.users, nil)
}
}
if (diff.type == .removed) {
print("Removed user: \(diff.document.data())")
}
}
}
}
However my array always return 1 if there's only one user modified and my collectionview reload and then only show one user! How do I
return all the users even if only one was modified?
Thanks
New Function:
func fetchUsers( complete: #escaping ( _ success: Bool, _ users: [User], _ error: Error? )->()) {
self.users = []
let circleId = UserDefaults.standard.string(forKey: "circleId") ?? ""
DataService.call.REF_CIRCLES.document(circleId).collection("insiders").order(by: "position", descending: false).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documents.forEach { diff in
let data = diff.data()
let id = diff.documentID
let user = User(key: id, data: data)
self.users.append(user)
complete(true, self.users, nil)
}
}
}
Now I'm seeing a lot of duplicate users in my collectionview
You're looping over QuerySnapshot.documentChanges, which only contains documents that changed since the last snapshot.
To get all documents in the query (instead of just the modified ones), loop over QuerySnapshot.documents instead:
DataService.call.REF_CIRCLES.document(circleId).collection("insiders").order(by: "position", descending: false).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documents.forEach { diff in
...
Related
I try to find a solution for paginate a firebase query on ios/swift but I couldn't build algorithm for my state.
My method is like this:
func downloadData(completion: #escaping ([Post]) -> Void) {
// download data with pagination
let firestoreDatabase = Firestore.firestore()
var first = firestoreDatabase.collection("posts").order(by: "date", descending: true).limit(to: 5)
first.addSnapshotListener{ snapshot, error in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
self.postList.removeAll(keepingCapacity: false)
DispatchQueue.global().async {
for document in snapshot.documents {
// getting data from document stuff ...
self.postList.append(self.post)
}
completion(self.postList)
}
// how can I repeat this query as long as lastSnapshot exist
firestoreDatabase.collection("posts").order(by: "date", descending: true).start(afterDocument: lastSnapshot).addSnapshotListener { querySnapshot, error in
}
}
}
I tried following mindset but it didn't work, and entered an infinite loop. I didn't understand why it is.
func downloadData(completion: #escaping ([Post]) -> Void) {
// download data with pagination
let firestoreDatabase = Firestore.firestore()
var first = firestoreDatabase.collection("posts").order(by: "date", descending: true).limit(to: 5)
first.addSnapshotListener{ snapshot, error in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
self.postList.removeAll(keepingCapacity: false)
DispatchQueue.global().async {
for document in snapshot.documents {
// geting data from document stuff ...
self.postList.append(self.post)
}
completion(self.postList)
}
repeat {
firestoreDatabase.collection("posts").order(by: "date", descending: true).start(afterDocument: lastSnapshot).addSnapshotListener { querySnapshot, error in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
self.postList.removeAll(keepingCapacity: false)
DispatchQueue.global().async {
for document in snapshot.documents {
// getting data from document stuff ...
self.postList.append(self.post)
}
completion(self.postList)
}
lastSnapshot = snapshot.documents.last
}
} while(lastSnapshot.exists)
}
}
I think lastSnapshot must be nil after the query loop but it is appear that it is still exist.
how can I fix lastSnapshot problem? Or is there different mindset / easiest way to paginate?
In firebase documents, it says just use this but how can we repeat query that has " .start(afterDocument: lastSnapshot) " stuff?
First and foremost, for plain-vanilla pagination, don't use a snapshot listener when fetching documents. You can paginate documents with a snapshot listener but the process is more complex.
I've embedded my notes into the comments in the code below for clarity.
let pageSize = 5
var cursor: DocumentSnapshot?
func getFirstPage(completion: #escaping (_ posts: [Post]?) -> Void) {
let db = Firestore.firestore()
let firstPage = db.collection("posts").order(by: "date", descending: true).limit(to: pageSize)
firstPage.getDocuments { snapshot, error in
guard let snapshot = snapshot else {
// Don't leave the caller hanging on errors; return nil,
// return a Result, throw an error, do something.
completion(nil)
if let error = error {
print(error)
}
return
}
guard !snapshot.isEmpty else {
// There are no results and so there can be no more
// results to paginate; nil the cursor.
cursor = nil
// And don't leave the caller hanging, even on no
// results; return an empty array.
completion([])
return
}
// Before parsing the snapshot, manage the cursor.
if snapshot.count < pageSize {
// This snapshot is smaller than a page size and so
// there can be no more results to paginate; nil
// the cursor.
cursor = nil
} else {
// This snapshot is a full page size and so there
// could potentially be more results to paginate;
// set the cursor.
cursor = snapshot.documents.last
}
var posts: [Post] = []
for doc in snapshot.documents {
posts.append(newPost) // pseudo code
}
completion(posts)
}
}
func continuePages(completion: #escaping (_ posts: [Post]?) -> Void) {
guard let cursor = cursor else {
return
}
let db = Firestore.firestore()
let nextPage = db.collection("posts").order(by: "date", descending: true).limit(to: pageSize).start(afterDocument: cursor)
nextPage.getDocuments { snapshot, error in
guard let snapshot = snapshot else {
completion(nil)
if let error = error {
print(error)
}
return
}
guard !snapshot.isEmpty else {
// There are no results and so there can be no more
// results to paginate; nil the cursor.
cursor = nil
completion([])
return
}
// Before parsing the snapshot, manage the cursor.
if snapshot.count < pageSize {
// This snapshot is smaller than a page size and so
// there can be no more results to paginate; nil
// the cursor.
cursor = nil
} else {
// This snapshot is a full page size and so there
// could potentially be more results to paginate;
// set the cursor.
cursor = snapshot.documents.last
}
var morePosts: [Post] = []
for doc in snapshot.documents {
morePosts.append(newPost) // pseudo code
}
completion(morePosts)
}
}
Hello there I have nested database with collection(quotes)>document(uid)>collection(quote)>document(id)
When I try to fetch the quote, I can only fetch for current user. How can I loop through uid and get everything inside quote collection for every user.
My code for fetching the quotes:
func fetchQuote() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
Firestore.firestore().collection("quotes")
.document(uid).collection("quote")
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("There was an error while fetch the quotes.")
return
}
querySnapshot?.documentChanges.forEach({ change in
if change.type == .added{
let data = change.document.data()
self.quotes.append(.init(documentId:change.document.documentID, data: data))
}
})
}
}
I tried to remove the following:
.document(uid).collection("quote")
What I did is use of .collectionGroup()
Firestore.firestore().collectionGroup("quote").getDocuments(){ querySnapshot, error in
if let error = error {
print("There was an error \(error)")
return
}
querySnapshot?.documentChanges.forEach({ change in
if change.type == .added{
let data = change.document.data()
self.quotes.append(.init(documentId:change.document.documentID, data: data))
}
})
}
My database is currently organized into two collections: male_users & female_users. When the app is first launched AND the user is already logged in, I attempt to pull their usernode from the database. The problem I am facing is, at this time I don't know whether to search the MALE_COLLECTION or FEMALE_COLLECTION to find the user. What would be the proper way of working around this? Should I use user defaults to save the gender of the last user?
static func fetchUser(withUid uid: String, completion: #escaping (User) -> Void) {
COLLECTION_MALE_USERS.document(uid).getDocument { (snapshot, error) in
if let userNode = snapshot?.data() {
guard let user = User(with: userNode) else {
print("DEBUG: Failed to create user")
return
}
completion(user)
}
else {
COLLECTION_FEMALE_USERS.document(uid).getDocument { snapshot, error in
guard let userNode = snapshot?.data() else {
print("DEBUG: No user node found")
return
}
guard let user = User(with: userNode) else {
print("DEBUG: Failed to create user")
return
}
completion(user)
}
}
}
}
I would suggest doing something like Ahmed Shendy suggested but I'm not familiar with geofire.
You could use something like shown below or better yet, move the second fetch to a new function and call that new function after fetching for male users produces no results.
func fetchUser(uid: String, onSuccess: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
let maleDocRef = COLLECTION_MALE_USERS.document(uid)
let femaleDocRef = COLLECTION_FEMALE_USERS.document(uid)
maleDocRef.getDocument { (document, error) in
if let error = error {
onError(error.localizedDescription)
return
}
if let document = document, document.exists {
print("male user exists")
guard let data = document.data() else { return }
let user = User(with: data)
onSuccess(user)
} else {
print("male user does no exists")
// BOF second fetch
femaleDocRef.getDocument { (document, error) in
if let error = error {
onError(error.localizedDescription)
return
}
if let document = document, document.exists {
print("female user exists")
guard let data = document.data() else { return }
let user = User(with: data)
onSuccess(user)
} else {
print("no user either male or female exist")
}
}
// EOF second fetch
}
}
}
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
}
I have migrated user post, followers and following from from firebase to firestore. Now i have migrated post, followers and following and post, followers count too.
Here the code i have migrated from firebase to firestore.
import Foundation
import FirebaseDatabase
import FirebaseFirestore
class FollowApi {
var REF_FOLLOWERS = Database.database().reference().child("followers")
var REF_FOLLOWING = Database.database().reference().child("following")
let db = Firestore.firestore()
func followAction(withUser id: String) {
let docRef = db.collection("user-posts").document(id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
self.db.collection("feed").document(API.User.CURRENT_USER!.uid).setData([document.documentID: true])
} else {
print("Document does not exist")
}
}
self.db.collection("followers").document(id).setData([API.User.CURRENT_USER!.uid: true])
self.db.collection("following").document(API.User.CURRENT_USER!.uid).updateData([id: true])
}
func unFollowAction(withUser id: String) {
let docRef = db.collection("user-posts").document(id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
self.db.collection("feed").document(API.User.CURRENT_USER!.uid).delete()
} else {
print("Document does not exist")
}
}
self.db.collection("followers").document(id).setData([API.User.CURRENT_USER!.uid: NSNull()])
self.db.collection("following").document(API.User.CURRENT_USER!.uid).setData([id: NSNull()])
}
func isFollowing(userId: String, completed: #escaping (Bool) -> Void) {
let docRef = db.collection("followers").document(userId)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("documnetData::::\(String(describing: document.data()))")
if let dataDescription = document.data(), let _ = dataDescription[API.User.CURRENT_USER!.uid] as? Bool {
completed(true)
}
completed(false)
} else {
completed(false)
}
}
}
func fetchCountFollowing(userId: String, completion: #escaping (Int) -> Void) {
// REF_FOLLOWING.child(userId).observe(.value, with: {
// snapshot in
// let count = Int(snapshot.childrenCount)
// completion(count)
// })
db.collection("following").document(userId).getDocument { (querySnapshot, error) in
let count = Int((querySnapshot?.documentID)!)
print("followingCount::::\(String(describing: count))")
completion(count!)
}
}
}//followAPI
I tried to get following counts from firestore.
let count = Int((querySnapshot?.documentID)!)
print("followingCount::::\(String(describing: count))")
completion(count!)
but does not show any any yet all. I do not know what mistake i have done ?
Any help much appreciated pls....
If you're querying for a collection then its snapshot will contain an array of documents. What are you trying to get is a documentID which is same as key in Firebase.
Firestore | Firebase
documentID = snapshot.key
documentData = snapshot.value
Now, Come to the main point and here is what you need to get the count.
let count = querySnapshot?.documents.count
print(count)
EDIT For Comment: how can i migrate REF_FOLLOWING.child(userId).observe(.value, with: { snapshot in let count = Int(snapshot.childrenCount) completion(count) }) to firestore
Based on attached DB structure you're fetching following corresponding to userId which is a Document.
REF_FOLLOWING.document(userId).getDocument { (snapshot, error) in
if let _error = error {
print(_error.localizedDescription)
return
}
guard let _snapshot = snapshot else {return}
/// This is a single document and it will give you "[String:Any]" dictionary object. So simply getting its count is the result you needed.
let dict = _snapshot.data()
print(dict.count)
}