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.
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)
}
}
I'm trying with no success on finding a way to retrieve only a single document instead of an array of documents from Firestore below is the code that I'm using for fetching ad array. Someone has suggestion on how to change fro getting only a document?
#Published var plantData: [PlantDataModel] = [] -> here I don't want an array
func loadData() {
print("FIREBASE LOADING DETAIL DATA VIEW")
db.collection("plantsData").whereField("plantId", isEqualTo: plant.idPlant).addSnapshotListener { querySnapshot, error in
if let querySnapshot = querySnapshot {
self.plantData = querySnapshot.documents.compactMap { document in
do {
let x = try document.data(as: PlantDataModel.self)
return x
} catch let error {
print("Errore fetching data: \(error)")
}
return nil
}
}
}
}
thank you
Replace
self.plantData = querySnapshot.documents.compactMap { document in
do {
let x = try document.data(as: PlantDataModel.self)
return x
} catch let error {
print("Errore fetching data: \(error)")
}
return nil
}
With
if let first = querySnapshot.documents.first {
do {
let x = try first.data(as: PlantDataModel.self)
self.plantData.append(x)
} catch let error {
print("Errore fetching data: \(error)")
}
}
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
This is my first time working with a cloud database and I'm looking for a little bit of guidance here as I'm relatively new to programming, and Firestore in particular.
I'm trying to get() all of my document data inside of my viewDidLoad and store it inside of a dictionary so that I can use it later in the tableView dataSource methods to populate my tableView Sections and Rows.
I'm working on a gym/workout log app and I inputted some dummy data for Days and Workouts Collections, so my dictionary prints out like this...
dataDict = ["Monday": ["Chest", "Arms"], "Wednsday": ["Legs", "Arms"], "Tuesday": ["Back"]]
But I'm having trouble using this data to populate the fields because if I try to print out the results of dataDict outside of the dateWorkoutRequest function, like inside of my dataSource methods, I get an empty dictionary. Is my tableView.reloadData() in the wrong place? Should I be using a dictionary to parse my data or is that a bad idea?
Here is my data structure and the relevant code...
/users/mi9P3TrLwkQejYo3oDIu/Days/WZ3Q6LDuu1kja5Rc/Workouts/BpLGFREoJNzNQW
var daysArray = [String]()
var dayIdArray = [String]()
var dataDict : [String:[String]] = [:]
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
vcBackgroundImg()
navConAcc()
picker.delegate = self
picker.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
tableView.tableFooterView = UIView()
Auth.auth().addStateDidChangeListener { (auth, user) in
self.userIdRef = user!.uid
self.colRef = Firestore.firestore().collection("/users/\(self.userIdRef)/Days")
self.dateWorkoutRequest()
}
}
func dateWorkoutRequest(){
self.colRef.getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else {
//Appending all Days collection documents with a field of "dow" to daysarray...
for dayDocument in snapshot!.documents {
self.daysArray.append(dayDocument.data()["dow"] as? String ?? "")
self.dayIdArray.append(dayDocument.documentID)
Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else {
//Assigning all Workouts collection documents belonging to selected \(dayDocument.documentID) to dictionary dataDict...
for document in snapshot!.documents {
if self.dataDict[dayDocument.data()["dow"] as? String ?? ""] == nil {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""] = [document.data()["workout"] as? String ?? ""]
} else {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""]?.append(document.data()["workout"] as? String ?? "")
}
print(self.dataDict)
}
}
}
}
self.dayCount = snapshot?.count ?? 0
self.tableView.reloadData()
}
}
}
I would try to put the self.tableView.reloadData() just after the line that you append document in the array.
I think that it's interesting you put DispatchQueue.main.async block in the reloadData too.
Like this:
func dateWorkoutRequest(){
self.colRef.getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else {
//Appending all Days collection documents with a field of "dow" to daysarray...
for dayDocument in snapshot!.documents {
self.daysArray.append(dayDocument.data()["dow"] as? String ?? "")
self.dayIdArray.append(dayDocument.documentID)
Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else {
//Assigning all Workouts collection documents belonging to selected \(dayDocument.documentID) to dictionary dataDict...
for document in snapshot!.documents {
if self.dataDict[dayDocument.data()["dow"] as? String ?? ""] == nil {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""] = [document.data()["workout"] as? String ?? ""]
} else {
self.dataDict[dayDocument.data()["dow"] as? String ?? ""]?.append(document.data()["workout"] as? String ?? "")
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
self.dayCount = snapshot?.count ?? 0
}
}
}
So I have done this on my android app (and it works), to populate a list with the document names from a collection
db.collection("usersAuth/${FirebaseAuth.getInstance().uid!!}/KitLists")
.addSnapshotListener(EventListener<QuerySnapshot> { value, e ->
if (e != null) {
Log.w("TAG", "Listen failed.", e)
return#EventListener
}
for (document in value.documents) {
val data = document
val kitName = data.id
firstKitList.add(kitName)
}
mainListViewAdapter.notifyDataSetChanged()
})
I am trying to do the same on my iOS version but I don't know whats wrong
override func viewWillAppear(_ animated: Bool) {
setListener()
}
func setListener() {
db.collection("usersAuth/\(String(describing: Auth.auth().currentUser))/KitLists")
.addSnapshotListener { (snapshot, error ) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
} else {
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
let kitListName = data["KitLists"] as? String
let newLists = KitList(kitListName: kitListName!)
self.lists.append(newLists)
}
self.tableView.reloadData()
}
}
}
any ideas? Thanks
-- EDIT
Firestore
Firestore2
You need to get the uid from the currentUser, for example:
if let userId = Auth.auth().currentUser.uid {
db.collection("usersAuth").document(userId).collection("KitLists")
.addSnapshotListener { (snapshot, error ) in
//...
}
To get the KitLists documentId
for document in snap.documents {
let documentName = document.documentID // <--- This
let newLists = KitList(kitListName: documentName)
self.lists.append(newLists)
}