Check if a Firestore query (whereField isEqualTo) did find no documents - ios

I want to check if my Firestore query did find any documents with the specific fields I want or not. If not I would like to proceed to some other code.
Unfortunately I haven't found a solution myself to this problem. Can you help?
Code:
Firestore.firestore().collection("conversations").whereField("mainUserID", isEqualTo: MainUID)
.whereField("otherUserID", isEqualTo: otherUserId).getDocuments { (snapshot, err) in
if snapshot.exists == true { // Value of type 'QuerySnapshot?' has no member 'exists'
} else {
}
}

From the docs
A FIRQueryDocumentSnapshot contains data read from a document in your
Firestore database as part of a query. The document is guaranteed to
exist and its data can be extracted with the data property or by using
subscript syntax to access a specific field.
A FIRQueryDocumentSnapshot offers the same API surface as a
FIRDocumentSnapshot. As deleted documents are not returned from
queries, its exists property will always be true and data: will never
return nil.
with the important bit being this
The document is guaranteed to exist
so therefore a .exists option would not make sense due to the guaranteed existence of the snapshot.
One approach is to check how many documents are in the snapshot
if docs.count > 0 {
//there are docs
} else {
//there are no docs
}

Related

How to structure data in Firestore using swift [duplicate]

The documentation does not have any examples on how to add a subcollection to a document. I know how to add document to a collection and how to add data to a document, but how do I add a collection (subcollection) to a document?
Shouldn't there be some method like this:
dbRef.document("example").addCollection("subCollection")
Edit 13 Jan 2021:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
Assuming we have a chat application that has a database structure that looks similar to this:
To write a subCollection in a document, please use the following code:
DocumentReference messageRef = db
.collection("rooms").document("roomA")
.collection("messages").document("message1");
Creating a messages collection and calling addDocument() 1000 times will be expensive for sure, but this is how Firestore works. You can switch to Firebase Realtime Database if you want where the number of writes doesn't matter. But regarding Supported Data Types in Firestore, in fact, you can use an array because it is supported. In Firebase Realtime database you could also use an array, but this is an anti-pattern. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
Cloud Firestore can store arrays, but it does not support querying array members or updating single array elements. However, you can still model this kind of data by leveraging the other capabilities of the Cloud Firestore. Here is the documentation where it is very well explained.
You also cannot create a subcollection with 1000 messages, add all of them to the database, and expect it to be considered a single record. It will be considered one write operation for every message, in total 1000 operations. The picture above does not show how to retrieve data, it shows a database structure in which you have something like this:
collection -> document -> subCollection -> document
Here's a variation where the subcollection is storing ID values at the collection level, rather than within a document where the subcollection is a field there with additional data.
This is useful for connecting a 1-to-Many ID mapping w/out having to drill through an additional document:
function fireAddStudentToClassroom(studentUserId, classroomId) {
var db = firebase.firestore();
var studentsClassroomRef =
db.collection('student_class').doc(classroomId)
.collection('students');
studentsClassroomRef
.doc(studentUserId)
.set({})
.then(function () {
console.log('Document Added ');
})
.catch(function (error) {
console.error('Error adding document: ', error);
});
}
Thanks to #Alex's answer
This answer a bit off from the original question here, where it explicitly asks for adding a collection to a document. However, after searching for a solution for this scenario and not finding any mention in docs or on SO, this post seems like a reasonable place to share the findings
Here's my code:
firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).set(wordData)
.then(function() {
console.log("Collection added to Firestore!");
var promises = [];
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('AudioSources').doc($scope.accentDialect).set(accentDialectObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('FunFacts').doc($scope.longLanguage).set(funFactObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('Translations').doc($scope.translationLongLanguage).set(translationObject));
Promise.all(promises).then(function() {
console.log("All subcollections were added!");
})
.catch(function(error){
console.log("Error adding subcollections to Firestore: " + error);
});
})
.catch(function(error){
console.log("Error adding document to Firestore: " + error);
});
This makes a collection EnglishWords, which has a document of. The document of has three subcollections: AudioSources (recordings of the word in American and British accents), FunFacts, and Translations. The subcollection Translations has one document: Spanish. The Spanish document has three key-value pairs, telling you that 'de' is the Spanish translation of 'of'.
The first line of the code creates the collection EnglishWords. We wait for the promise to resolve with .then, and then we create the three subcollections. Promise.all tells us when all three subcollections are set.
IMHO, I use arrays in Firestore when the entire array is uploaded and downloaded together, i.e., I don't need to access individual elements. For example, an array of the letters of the word 'of' would be ['o', 'f']. The user can ask, "How do I spell 'of'?" The user isn't going to ask, "What's the second letter in 'of'?"
I use collections when I need to access individual elements, a.k.a. documents. With the older Firebase Realtime Database, I had to download arrays and then iterate through the arrays with forEach to get the element I wanted. This was a lot of code, and with a deep data structure and/or large arrays I was downloading tons of data that I didn't need, and slowing my app running forEach loops on large arrays. Firestore puts the iterators in the database, on their end, so that I can request a single element and it sends me just that element, saving me bandwidth and making my app run faster. This might not matter for a web app, if your computer has a broadband connection, but for mobile apps with poor data connections and slow devices this is important.
Here are two pictures of my Firestore:
From the docs:
You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists.
Here i faced the same issue and solve with the answere of #Thomas David Kehoe
db.collection("First collection Name").doc("Id of the document").collection("Nested collection Name").add({
//your data
}).then((data) => {
console.log(data.id);
console.log("Document has added")
}).catch((err) => {
console.log(err)
})
too late for an answer but here is what worked for me,
mFirebaseDatabaseReference?.collection("conversations")?.add(Conversation("User1"))
?.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.id)
mFirebaseDatabaseReference?.collection("conversations")?.document(documentReference.id)?.collection("messages")?.add(Message(edtMessage?.text.toString()))
}?.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
add success listener for adding document and use firebase generated ID for a path.
Use this ID for the complete path for a new collection you want to add.
I.E. - dbReference.collection('yourCollectionName').document(firebaseGeneratedID).collection('yourCollectionName').add(yourDocumentPOJO/Object)
Okay so I recently faced a similar problem given the recent update in the firebase/firestore documentation.
And here is a solution that worked for me
const sendMessage = async () => {
await setDoc(doc(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME, nanoid()), {
text:'this is a sample text',
createdAt: serverTimestamp(),
name: currentUser?.firstName + ' ' + currentUser?.lastName,
photoUrl: currentUser?.photoUrl,
userId: currentUser?.id,
});
}
You can find a similar example in the docs
https://firebase.google.com/docs/firestore/data-model#web-version-9_3
chat room
If you wish to listen for live update you can use a similar method as follows
const messagesRef = collection(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME)
const liveUpdate = async () => {
const queryObj = query(messagesRef, orderBy("createdAt"), limit(25));
onSnapshot(queryObj, (querySnapshot) => {
const msgArr: any = [];
querySnapshot.forEach((doc) => {
msgArr.push({ id: doc.id, ...doc.data() })
});
console.log(msgArr);
});
}
There is no separate method to add sub-collection into the document.
You can just call the collection method itself.
If the collection exists it will reference that otherwise create a new one.
dbRef.document("example").collection("subCollection")

How to extract the first row in a DynamoDB table?

I am using (AWS) DynamoDB for the first time, so my question is rather basic.
I have set up a table (myTable) containing one field (theField) and I am able to fill it up one record at a time.
Here is what I want to do: make a query to extract the first element of the sorted table. I guess it could hardly be simpler.
This is my code, based on what I could find in the AWS documentation and on some example from the net:
let queryExpression = AWSDynamoDBQueryExpression()
queryExpression.scanIndexForward = false
queryExpression.limit = 1
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.query(myTable.self, expression: queryExpression) {
(output: AWSDynamoDBPaginatedOutput?, error: Error?) in
if error != nil {
print("The request failed. Error: \(String(describing: error))")
}
if output != nil {
// Process the output.
}
}
When I run this code I get the error message below:
The request failed. Error: Optional(Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=0 "(null)"
UserInfo={__type=com.amazon.coral.validate#ValidationException, message=Either the KeyConditions or
KeyConditionExpression parameter must be specified in the request.})
First what is this KeyConditions or KeyConditionExpression business?
The query is clear why do I need any condition?
Second, I of course tried to fill some dummy condition (based on what I could find on some other post) to see what happens, but it never worked.
Could someone tell me how I need to write queryExpression to do what I want?
You have no query criteria. For a query you need to provide at least the PartitionKey. Try a scan instead of a query.
You cannot extract first item from a dynamodb. Not with scan or query. You have to scan the table entirely or create a GSI with createdAt as hashKey/sortKey

Search Firebase Query with array of unique keys

I have my schema set as follows.
Now i want to send the a string chinohills7leavescafe101191514.19284 and want to check if there is any string in the Chino Hills or not.
I am confused to make any search query because i have not stored above string in fixed childKey
I know the schema should be like this but i cannot change the schema.
leaves-cafe
codes
Chino Hills
-Kw0ZtwrPjyNh1_HJrkf
codeValue: "chinohills7leavescafe101191514.19284"
You're looking for queryOrderedByValue. It works the same ways as queryOrderedByChild and allows you to use queryEqualToValue to achieve the result you need since you can't alter your current schema.
Here's an example
// warning: untested code - just illustrating queryOrderedByValue
let ref = Database.database().reference().child("leaves-cafe").child("codes").child("Chino Hills")
let queryRef = ref.queryOrderedByValue().queryEqual(toValue: "chinohills7leavescafe101191514.19284")
queryRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("value does exists")
} else {
print("value doesn't exist")
}
})
Your alternative option is to iterate over all nodes and check if the value exists manually as 3stud1ant3 suggested. However, note that this approach is both costy and a security risk. You would be downloading potentially a lot of data, and generally you shouldn't load unneeded data (especially if they're sensitive information, not sure if that's your case) on device; it's the equivalent of downloading all passwords off a database to check if the entered one matches a given user's.

MapKit Define the desired type of search results (Country, city, region, etc)

For an app i'm building, I want to implement a feature that allows users to specify the geographical origin of wines (country (e.g. France), region (e.g. Bordeaux), subregion (e.g. Paullac)).
I want to make sure that I don't have to add all available countries myself, and that all information that comes into the database is valid. Therefore, I decided to do it as follows:
User adds a new wine and types the name of the country it comes from
While typing, the app searches in the apple maps database
The results from this search get displayed as suggestions, and when the user taps a suggestion, the app creates a Country object with all relevant information. The wine van only be saved when such an object is present
This works fine, except one thing: Apple maps returns anything, like restaurants, shops, etcetera, from anywhere.
My question: How can I specify WHAT I am looking for? I can only specify the region I'm searching in, which is irrelevant in my case. I would like to be able to tell apple maps to ONLY look for countries, regions, cities, whatever. Is this possible in a way? I have exhausted google for this and found no way thus far.
Going off what #Trevor said, I found rejecting results where either the title or subtitle have numbers yields pretty good results if you only want cities and towns.
Swift 4.1 code:
// Store this as a property if you're searching a lot.
let digitsCharacterSet = NSCharacterSet.decimalDigits
let filteredResults = completer.results.filter { result in
if result.title.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
return true
}
or more compactly:
let filteredResults = completer.results.filter({ $0.title.rangeOfCharacter(from: digitsCharacterSet) == nil && $0.subtitle.rangeOfCharacter(from: digitsCharacterSet) == nil })
The best solution we found was to filter our results using a comma in the result's title. This mostly returned only results that matched a city's format, e.g Detroit, MI, United States. We added this filter to the ones suggested by #Ben Stahl. Ben's solution filtered out edge cases where a comma formed part of the business' name.
This usually returns the correct result within three characters. To answer the OP's question, you could then parse this string by city, state or country to get the desired result.
For better results you could use the Google Places API.
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
self.searchResults = completer.results.filter { result in
if !result.title.contains(",") {
return false
}
if result.title.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
return true
}
self.searchResultsCollectionView.reloadData()
}
I have worked with MapKit and don't believe you can do autocomplete assistance on user entries as they type the best solution I found is Google Place API autocomplete
iOS right now provides receiving geo-coordinates when sending a well-formatted address , or you can receive an address when sending a pair of coordinates. Or points of interest for locations names or coordinates.
There was a class added to MapKit in iOS 9.3 called MKLocalSearchCompleter which helps with autocompletion. You can filter what is returned by using 'MKSearchCompletionFilterType' but that isn't the most extensive and might not fully help with your situation. It does return cities and countries as results but it also returns businesses when I've used it.
One possible option is to filter the returned results again on the app side and exclude all results that have a numeric character in them.
func setupCompleter() {
self.searchCompleter = MKLocalSearchCompleter()
self.searchCompleter?.delegate = self
self.searchCompleter?.filterType = .locationsOnly
self.searchCompleter?.queryFragment = "Bordeaux"
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
print("Results \(completer.results)")
// Do additional filtering on results here
}
In addition to Allan's answer, I've found that if you filter by the subtitle property of a MkLocalSearchCompletion object, you can remove the business entries.

Check Firebase for existing data before overwriting?

I have the following data in Firebase:
devices
iphone5
date_created: "1456183905"
I'm trying to determine if "date_created" exists, and if it doesn't then create it.
I read about snapshots, but is there an easier way to check Firebase to see if this data exists? What I have now is using snapshots, but it is tied to an event handler. Can't I just do a basic query to see if this entry exists or not?
Thanks.
You can test if a value exists in your Swift code, by:
let ref = Firebase(url: "https://stackoverflow.firebaseio.com/35570687")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
if (!snapshot.exists()) {
ref.setValue([".sv": "timestamp"])
}
else {
print("already exist")
}
})
But since this is only client-side, there is a chance that two clients will run the code at almost the exact time and both end up writing the timestamp. In the snippet above that wouldn't be a problem, but in real use-cases this sort of race condition might be unwanted.
As Andre commented, you can validate this in your security rules:
"date_created": {
".write": "!data.exists() || data.val() == newData.val()"
This validates that either this is the first time you write date_created (so the data won't exist yet) or otherwise that the value is unchanged.

Resources