My Firebase database contains 2 collections of name "collectionA" and "collectionB", and they both contain different documents. They both have rules to allow authenticated users to read and write (however I don't get permission erros anyway). The problem is when I get a reference to collectionA and B as such:
var docRefA = db.collection("collectionA")
var docRefB = db.collection("collectionB")
Collection A retrieves docs with its respective documents, but collection B returns empty docs. I can still write to collection B succesfully though, so I know the docRef isn't wrong. I'll attach an image of the actual collections in case there is any differences between the collections I'm not aware of:
collection A and B - "users" collection would be collectionA and "store_exercises" would be B. The only difference I see is the documents in B are greyed out and italic, not sure what this could mean?
EDIT 1: This is how I generate/add items to collection A (I can see both write operations work, it's only reading):
val userMap = HashMap<String, String>()
userMap["username"] = username
userMap["email"] = email
db.collection("users").document(auth.currentUser!!.uid).set(userMap)
and collection B:
db.collection("store_exercises").document("whatever").collection("another_collection").document("name")
.set(myObject)
EDIT 2: Image of contents of the two collections:
contents of both collections
Else, how can I debug a query? or test this scenario?
Ok, just figured that one out, if a document doesn't contain fields, and just collections (even if those collections are composed of non empty documents), then it will think its an "empty branch" and not display anything chained in there.
I only wanted 2 collections so I had to create a document to link them so I didn't add any fields, but unless there's some notation or something it seems like adding at least one field is a must for them to be read. All I did was add one field to the documents in collection B, and now they're not empty.
Related
Question
I have a collection named Users with a field named friendEmails which is an array that contains Strings.
I have a document with friendEmails = {joe#gmail.com, dan#gmail.com}, and I want to append newEmails = {mat#gmail.com, sharon#gmail.com} to it.
Problem
The only options I know for this are:
Reading the friendEmails array first, then adding the union of it and newEmails to the document.
Iterating over the elements of newEmails (let's and each iteration doing:
myCurrentDocumentReference.update(FieldValue.arrayUnion, singleStringElement);
(Since FieldValue.arrayUnion only lets me pass comma-separated elements, and all I have is an array of elements).
These two options aren't good since the first requires an unnecessary read operation (unnecessary since FireStore seems to "know" how to append items to arrays), and the second requires many write operations.
The solution I'm looking for
I'd expect Firestore to give me the option to append an entire array, and not just single elements.
Something like this:
ArrayList<String> newEmails = Arrays.asList("mat#gmail.com", "sharon#gmail.com");
void appendNewArray() {
FirebaseFirestore firestore = FirebaseFirestore.getInstance();
firestore.collection("Users").document("userID").update("friendEmails", FieldValue.arrayUnion(newEmails));
}
Am I missing something? Wouldn't this be a sensible operation to expect?
How else could I go about performing this action, without all the unnecessary read/write operations?
Thanks!
You can add multiple items to an array field like this using FieldValue.arrayUnion() as the value of a field update:
docRef.update("friendEmails", FieldValue.arrayUnion(email1, email2));
If you need to convert an ArrayList to an array for use with varargs arguments:
docRef.update("friendEmails", FieldValue.arrayUnion(
newEmails.toArray(new String[newEmails.size()])
));
Let's say that I have a document like this:
Document {
tags: list<Int> {0,1,2}
}
I want to change it to this:
Document {
tags: list<String> {SEASON, TRAINING, TOURNAMENT}
}
I have active users which uses the list of ints, How do I create a migration in Firestore for this problem?
One solution I have in mind is to make 2 migrations:
For creating a new tags called tagsStrings.
For deleting all users who still have tags.
But can I make it in 1?
I was unable to find documentation for this, on https://cloud.google.com/firestore/docs/manage-data/move-data
Thanks in advance
Firestore does not have a "migration" like SQL databases. The only way to modify data in existing documents, in bulk, is to:
Query for the documents to change
Iterate the results
Update each document with new values
Each one of these tasks should be straightforward.
You might also consider lazily updating each document as each are individually read during the normal course of your app's usage. So, if your app reads a document in the old format, immediately update it to the new format.
It's often helpful to have a dedicated field in each document to indicate which version of data that's contained within. So, initially set v=1 in each document, assign v=2 to mean that the document has strings instead of numbers for tags, then use that number to determine which documents have yet to be migrated.
I'm using a Firebase Firestore for android to store data. I tried to search data from documents.
My Firestore structure is:
Collection (Products) - Document (Auto Generated ID) - Field (NumberOfPeople,OfferStartDate,OfferEndDate,OfferPrice,Location)
I wrote query for search data on those fields.
CollectionReference collectionOfProducts = db.collection("Products");
collectionOfProducts
.whereEqualTo("Location", location)
.whereGreaterThanOrEqualTo("OfferPrice", offerPrice)
.whereLessThanOrEqualTo("OfferPrice", offerPrice)
.whereGreaterThanOrEqualTo("OfferStartDate", date)
.whereLessThanOrEqualTo("OfferEndDate", date)
.get()
I want search result like this: An offer which is between start date and end date, where offer price is greater than equal or less than equal on give price range. This query is not working in android studio.
How to do this in firestore firebase?
According to the official documentation regarding Cloud Firestore queries, please note that there some query limitations:
In a compound query, range (<, <=, >, >=) and not equals (!=, not-in) comparisons must all filter on the same field.
So a Query object that contains a call to both methods:
.whereGreaterThanOrEqualTo("OfferStartDate", date)
.whereLessThanOrEqualTo("OfferEndDate", date)
Is actually not possible, as "OfferStartDate" and "OfferEndDate" are different properties.
The best solution I can think of is to use only one of these method calls and do the other filtering on the client.
Another possible solution might be to use denormalization and duplicate the data
in certain intervals. In this way, you'll always know the time periods and you'll be able to create the corresponding queries.
To the best of my knowledge, Firestore only lets you use where<Greater/Less>ThanOrEqualTo() and where<Greater/Less>Than() a single field and all other filter operations on other fields can only be whereEqualTo().
Some workarounds for your specific case include -
1) Modifying your query to
collectionOfProducts
.whereGreaterThanOrEqualTo("OfferStartDate", date)
.whereEqualTo("Location", location)
.get()
And then performing the subsequent filtering on the result in your app code.
Alternately, you can perform your filter on "OfferPrice" and "Location" in your query and the remaining filters can be applied to the query result.
2) You can use firebase functions or other server code to write logic that performs customized filtering and fetch the result that way.
i was having same issue with this, but i found a work around that takes sometime to write.
lets say you want to search for a particular keyword(in this case the value of a field inside a document), and you want firebase to search multiple field instead of just looking for 1 particular field.
this is what you want to do.
const searchTerm = document.createElement('input')
db.collection('collection').where('field1', '==', `${searchTerm.value}`)
.get()
.then((snapshot) => {
if(snapshot.size === '0'){
db.collection('collection').where('field2', '==', `${searchTerm.value}`)
.get()
.then((snapshot) => {
if(snapshot.size === 0) {
db.collection......and repeat
}
})
}
})
in summary, the above code is basically telling js to search for the term again with a different field if the result of the previous query is 0. I know this solution might not be able to work if we have a large quantity of fields. But for folks out there that are working with small number fields, this solution might be able to help.
I really do hope firestore one day would allow such feature, but here is the code it worked fine for me.
the problem I have now is to allow search input to be able to search without have me to complete the word. I do currently have an idea how this would be, but just need some time to put together.
I am trying to build an android app that the user can enter a string, and a list emoji related to that string would show up. (Just like Venmo app) For example:
case 1: User enters "pizz", and in the list there would be "π", note that the users enter "pizz", not pizza!
case 2: User enters "rabb", and in the list there would be "π" and "π°", note that the users enter "rabb", not rabbit!
What would be a good data structure and algorithm for this problem?
A trie is what your looking for. From Wikipedia
A trie, also called digital tree and sometimes radix tree or prefix tree (as they can be searched by prefixes), is a kind of search treeβan ordered tree data structure ...
A trie is similar to a HashMap<K,V>, you can perform a lookup with keys and get a value. The difference is that you can also search by prefix. Given a prefix, it will find all the key-value pairs in the structure that have that prefix. It's basically the data structure for generating search suggestions.
General Idea:
Trie<String, String> t = new Trie<String, String>();
t.insert("pizza", "π");
t.insert("rabbit1", "π");
t.insert("rabbit2", "π°");
// then later...
t.findByPrefix("rabb"); // [π,π°]
Unfortunately, tries are too generic and are not present in any popular data structure libraries (like Java Collections Framework or Google Guava, for example). You'd have to implement one yourself or find an existing implementation and modify it.
I'd recommend:
Learning the theory. Watch this video. There are many more on YouTube that will teach you the basics. You can also search google for "N-way trie" and read notes about it.
Taking this class TrieST and modifying it. It's very similar (or already perfect) for what you need: http://algs4.cs.princeton.edu/52trie/TrieST.java.html see specifically thekeysWithPrefix method.
I have (somewhat) large list of jokes in my Firebase Database like in the image below.
I display them in a list in my Android app something like a feed. I also implemented possibility to log in with Firebase Authentication and now I want to add options for logged users to (dis)like jokes and add them to favorites (favorites are supposed to be like private bookmarks). I'm wandering how I could structure my data and I have two proposals:
Add new root node called "userJokes" with child nodes representing user UID from Firebase Authentication. Every UID child node should have copy of every joke from "joke" node with additional booleans representing (dis)like and favorite states.
Another solution is to add every user UID to a joke a user (dis)likes or adds to favorite.
First solution is logical, but how could I count number of likes and dislikes if I structure data this way? And what is the best way to copy every joke from "joke" node to "userJokes" node for every user to be showed in the feed? Second is impractical since while retrieving jokes, I will get info about every user that has (dis)liked or added to favorites and this is not what I need. Which solution is better? Is there any other? Is it OK to add user UID from Firebase Authentication to database in Firebase Database?
I think the first one is more accepted, although it needs some tweak :)
First note: if you create data only to be used as relational (like userJokes), it's better to just add simple value to it without copying entire source data (jokes data), like this:
userJokes: {
randomUserId: {
randomJokeId:true,
anotherRandomJokeId:true
}
awesomeUser: {
randomJokeId:true
}
}
Second note: if you want to implement two functionality (like and favorite), I think you should make it as different data. So it would be userJokeLike and userJokeFavorite (or something like that). And the structure for each of them should be same as I mentioned in first note.
In conclusion:
Every joke data is still in their source path (i.e. inside jokes) and ONLY their id is copied into newly created data path (userJokeLike and userJokeFavorite)
When you want to search for joke that user with id randomUserId likes, you should check for userJokeLike\randomUserId. Then from every joke id you got there, get the real data from inside source jokes path.
When you want to search for joke that is favorited by user with id randomUserId, basically, do the same as above.
When you want to count likes and favorite of each joke, just use something like this:
FirebaseDatabase.getInstance().getReference("userJokeLike")
.orderByChild().equalsTo("randomJokeId")
.addListenerForSingleValueEvent(new ValueEventListener() {
... onDataChange(DataSnapshot dataSnapshot) {
int jokeCount = dataSnapshot.getChildrenCount();
}
});
And there you go, hope this helps.
Note: I haven't check the last code, hope that work :p
EDIT:
Looks like I did misunderstand :D
The solution above is what I think is best for the sake of structure itself. But if we need something simple and fast, it is different for each case/situation. I think that the best solution if you want to get jokes with likes and favorites included (no need to create another request) then your structure should look like this:
jokes: {
randomJokeId: {
// joke data here
likes:{
randomUserId:true,
anotherUserId:true
},
favorites:{
randomUserId:true
}
}
}
It includes likes and favorite when you request jokes data. SO in each data you only need to check if current user's UID is exist inside likes and/or favorite. And the counter will be a lot easier this way.
Happy coding :)