Firestore search for String in Array in Document - ios

I want to search for an specific string value in an document which is in an array.
Here is my database:
This is my code so far: But it returns 0 documents:
func changePhotoUrlInPosts(url: String) {
let db = Firestore.firestore()
let user = UserService.currentUserProfile!
db.collection("posts")
.whereField("username", isEqualTo: user.username)
.getDocuments { (snapshot, error) in
if let indeedError = error {
print(indeedError.localizedDescription)
return
}
guard let indeedSnapshot = snapshot else {
print("snapshot is empty")
return
}
for document in indeedSnapshot.documents {
document.setValue(url, forKey: "photoUrl")
}
}
}
How can I go into my array in this document?
Thanks

Your screenshot is showing data in Realtime Database, but your code is querying Firestore. They are completely different databases with different APIs. You can't use the Firestore SDK to query Realtime Database. If you want to work with Realtime Database, use the documentation here.

There is author between posts and username field in your data structure.
Your code means that right under some specific post there is username field.
So such code will work because date right undes post:
db.collection("posts").whereField("date", isEqualTo: "some-bla-bla-date")
In your case you have two options as I see:
duplicate username and place this field on the same level as
date and guests.
re-write you code to check username inside author document.
Hope it will help you in your investigation.

So I changed my code to:
func loadData(url: URL){
let ref = Database.database().reference().child("posts")
let user = UserService.currentUserProfile!
ref.queryOrdered(byChild: "author/username").queryEqual(toValue: user.username).observe(.value, with: { snapshot in
if var post = snapshot.value as? [String : Any] {
print("updated all Posts")
post.updateValue(url.absoluteString, forKey: "photoUrl")
print(post.values)
}else{
print("fail")
}
})
}
It went through and I get the print statement of my values but the data didn't changed in the realtime database

Related

SwiftUI: How to iterate over Documents in Cloud Firestore Collection and get Data?

I have this small project where a user can post an Image together with a quote, I would then like to display the Image and the quote togehter in their profile, as well as somewhere else so other users can see the post.
If I have this Cloud Firestore setup
where all of the Image Docs have the same 3 fields, but with different values.
How can I then iterate over all of the Image Docs and get the the Url and the quote? So I later can display the url together with the correct Quote?
And if this is for some reason not possible, is it then possible to get the number of Documents in a Collection?
BTW, I am not very experienced so I would appreciate a "kid friendly" answer if possible
Firestore
.firestore()
.collection("Images")
.getDocuments { (snapshot, error) in
guard let snapshot = snapshot, error == nil else {
//handle error
return
}
print("Number of documents: \(snapshot.documents.count ?? -1)")
snapshot.documents.forEach({ (documentSnapshot) in
let documentData = documentSnapshot.data()
let quote = documentData["Quote"] as? String
let url = documentData["Url"] as? String
print("Quote: \(quote ?? "(unknown)")")
print("Url: \(url ?? "(unknown)")")
})
}
You can get all of the documents in a collection by calling getDocuments.
Inside that, snapshot will be an optional -- it'll return data if the query succeeds. You can see I upwrap snapshot and check for error in the guard statement.
Once you have the snapshot, you can iterate over the documents with documents.forEach. On each document, calling data() will get you a Dictionary of type [String:Any].
Then, you can ask for keys from the dictionary and try casting them to String.
You can wee that right now, I'm printing all the data to the console.
Keep in mind that getDocuments is an asynchronous function. That means that it runs and then returns at an unspecified time in the future. This means you can just return values out of this function and expect them to be available right after the calls. Instead, you'll have to rely on things like setting properties and maybe using callback functions or Combine to tell other parts of your program that this data has been received.
If this were in SwiftUI, you might do this by having a view model and then displaying the data that is fetched:
struct ImageModel {
var id = UUID()
var quote : String
var url: String
}
class ViewModel {
#Published var images : [ImageModel] = []
func fetchData() {
Firestore
.firestore()
.collection("Images")
.getDocuments { (snapshot, error) in
guard let snapshot = snapshot, error == nil else {
//handle error
return
}
print("Number of documents: \(snapshot.documents.count ?? -1)")
self.images = snapshot.documents.compactMap { documentSnapshot -> ImageModel? in
let documentData = documentSnapshot.data()
if let quote = documentData["Quote"] as? String, let url = documentData["Url"] as? String {
return ImageModel(quote: quote, url: url)
} else {
return nil
}
}
}
}
}
struct ContentView {
#ObservedObject var viewModel = ViewModel()
var body : some View {
VStack {
ForEach(viewModel.images, id: \.id) { item in
Text("URL: \(item.url)")
Text("Quote: \(item.quote)")
}
}.onAppear { viewModel.fetchData() }
}
}
Note: there are now fancier ways to get objects decoded out of Firestore using FirebaseFirestoreSwift and Combine, but that's a little outside the scope of this answer, which shows the basics

Reading data from Firestore in Swift

I am currently developing an IOS app, and I need a database! I've chosen the google firestore! I need to read some fields I create that have subfields!
Something like this:
db.collection("usersorders").document(uid).collection("order").addDocument(data: ["items":0, "order":["Book1":0,"Book2":0,"Book3":0]]){ (error) in
if error != nil {
// Show error message
print("Error saving user data")
}
}
Where I need to read the "Book1" value for example! I've looked in a lot of places, but I can't seem to find what I am looking for. Read subfields, from a field of a document!
#IBAction func AddtoCart(_ sender: Any) {
let uid = user!.uid
let docRef = db.collection("usersorders").document(user!.uid).collection("order").document()
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let Book1 = document.get("Book1")
let Items = document.get("items")
let Book1now = Book1 as! Int + 1
let Itemsnow = Items as! Int + 1
}
}}
This is what I have been doing but it doesn't work! After writing the code to update the database with the Items/Book1 now values it just doesn't update! Please Help me
Given that your document data looks like this:
["items":0, "order":["Book1":0,"Book2":0,"Book3":0]]
You'll first need to access the order field in your document, before you can then find an item in that field
let order = document.get("order")
As far as I can see, this makes order a dictionary, so you can get the specific value from it with:
let book1 = order["Book1"] as Int

Insert Firestore Document Data from Current User into Collection View

What Im trying to do is retrieve the Firestore data for a specific user (the user's books) and put it on a collection view like this picture. Currently I can only print the doc data on the console and thats where Im stuck. If you can help this is my Firestore data and my code. I didn't insert the collection view code because the collection view cell only has an image view (the books image). Thanks in advance :)
func fetchUserBooks() {
guard let uid = Auth.auth().currentUser?.uid else { return }
Firestore.firestore().collection("books").document(uid).getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data()
print(dataDescription ?? "")
} else {
print("Document does not exist")
}
}
}
You can use this pod: CodableFirebase to decode the document from Firestore. You need to create a struct/class that can hold the data coming from the db.
struct Book: Codable {
var description: String?
.....
}
Afterward, the method fetchUserBooks() could look like this:
Firestore.firestore().collection("books")....
guard let model = try? FirestoreDecoder().decode(Book.self, from: document.data()) else { return }
Keep in mind that you are working with async code, you need to use completion handlers.

How can I make a single query to retrieve data from two differences of Cloud Firestore collection?

Dear Swift and Cloud firestore developers,
I'm developing Chat app using swift and cloud firestore database. This is my collections:
Messages:
+ senderId
+ serceiverId
+ text
+ date
Users
+ userId
+ name
+ email
+ profilePic
+ phoneNumber
+ date
Let's say I'm logging in, my problem is that I want list all users (profilePic & name) who had chats history with me.
But is there any way I can use only single query of Firestore to retrieve the list of users?
Here is my current codes:
var usersList : [User]!
private lazy var usersRef = Firestore.firestore().collection("Users")
private lazy var messagesRef = Firestore.firestore().collection("Messages")
var uid = Auth.auth().currentUser?.uid as! String
messagesRef.whereField("senderId", isEqualTo: uid).order(by: "date_modified",
descending:true).addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let da = diff.document.data() as! NSDictionary
let receiverId = da["receiverId"] as! String
self.usersRef.whereField("userId", isEqualTo:
receiverId).getDocuments(completion: { (udata, error) in
for document in udata!.documents {
let du:[String:Any] = document.data()
let name = du["name"] as! String
let email = du["email"] as! String
self.usersList.append(User(userId: receiverId, name:
name, email:email))
self.tableView.reloadData()
}
})
}
}
}
}
}
There is no way to get data from two collections with a single read. You will either have to:
duplicate the necessary data from each user into each of their messages (known as denormalization), or
perform additional reads to get the user data, known as client-side joins.
Both of these are very normal on a NoSQL database. If you're new to the concept, I recommend:
reading NoSQL data modeling
watching Firebase for SQL developers. While this was created for the Firebase Realtime Database, the main concepts apply equally to Cloud Firestore.
watching the brand new What is a NoSQL Database? How is Cloud Firestore structured? video.

Firebase query using a list of ids (iOS)

I have an NSArray containing multiple ids. Is there a way in Firebase where I can get all the object with the ids in the array?
I am building a restaurant rating app which uses GeoFire to retrieve nearby restaurants. My problem is that GeoFire only returns a list of ids of restaurant that are nearby. Is there any way i can query for all the object with the ids?
No, you can't do a batch query like that in Firebase.
You will need to loop over your restaurant IDs and query each one using observeSingleEvent. For instance:
let restaurantIDs: NSArray = ...
let db = FIRDatabase.database().reference()
for id in restaurantIDs as! [String] {
db.child("Restaurants").child(id).observeSingleEvent(of: .value) {
(snapshot) in
let restaurant = snapshot.value as! [String: Any]
// Process restaurant...
}
}
If you are worried about performance, Firebase might be able to group all these observeSingleEvent calls and send them as a batch to the server, which may answer your original question after all ;-)
I know that this answer is considered accepted but I have had really good success using promise kit with the method frank posted with his javascript link Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly and just wanted to share the swift version
So I have a list of users ids that are attached to a post like this:
also these methods are in my post class where I have access to the post id from firebase
// this gets the list of ids for the users to fetch ["userid1", "userid2"....]
func getParticipantsIds() -> Promise<[String]> {
return Promise { response in
let participants = ref?.child(self.key!).child("people")
participants?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshotIds = snapshot.value as? [String] else {
response.reject(FirebaseError.noData)
return
}
response.fulfill(snapshotIds)
})
}
}
// this is the individual query to fetch the userid
private func getUserById(id:String) -> Promise<UserData> {
return Promise { response in
let userById = dbRef?.child("users").child(id)
userById?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else {
response.reject(FirebaseError.noData)
return
}
do {
let userData = try FirebaseDecoder().decode(UserData.self, from: value)
response.fulfill(userData)
} catch let error {
response.reject(error)
}
})
}
}
// this is the where the magic happens
func getPostUsers(compeltion: #escaping (_ users:[UserData], _ error:Error?) -> ()){
getParticipantsIds().thenMap { (id) in
return self.getUserById(id: id)
}.done { (users) in
compeltion(users, nil)
}.catch({ error in
compeltion([], error)
})
}

Resources