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)")
}
}
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))
}
})
}
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.
I get a critical error stating the following. I've tried everything but I can't seem to access the Movie struct as it says the parent 'Type' has no member called 'data', even though it clearly does.
"Value of type '[Type?]' has no member 'data'"
MODEL
struct SearchData: Decodable {
let data: [Type?]
}
struct Type: Decodable {
let data: [Movie?]
}
struct Movie: Decodable {
let title: String?
}
CONTROLLER
fileprivate var searchResults = [Movie?]()
func fetchTitles() {
let urlString = "https://www.what-song.com/api/search?limit=10&field=america"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
// if error occurs
if let err = err {
print("Failed to fetch titles", err)
return
}
// if success
guard let data = data else { return }
do {
let searchResult = try JSONDecoder().decode(SearchData.self, from: data)
self.searchResults = searchResult.data.data
print(searchResult)
} catch {
print("Failed to decode JSON:", error)
}
}.resume()
}
Try this :
var movieTitles = [String]()
for type in searchResult.data {
for movie in type.data {
guard let title = movie.title else { return }
print(title)
movieTitles.append(title)
}
}
you are doing a small mistake here I think
searchResult.data
will return you an array of
Type
You need to parse that array as well, something like this
searchResults = (searchResult.data[0]?.data)!
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)
}