I want to define these two collections in Cloud Firestore. Some sample data has been shown below:
Playlists:
name: "playlist1"
songCount: //To be calculated on real time
Songs:
title: "song1"
playlistId: "playlist1"
title: "song2"
playlistId: "playlist1"
title: "song3"
playlistId: "playlist1"
Further, in my Android app, I want to show separate lists of all playlists and all songs. However, while displaying the list of all playlists, I want to show the name of each playlist alongwith the number of songs contained in each, based on the value in "songCount".
Please advise what sort of query I need to make in Cloud Firestore to achieve this objective. Is there a JOIN function available or would I have to put the Songs collection in a loop and count the ones having playlistId as "playlist1", and then repeat it for all the playlists that are to be presented in the list of playlists? I want to have a smarter solution but couldn't find one on the internet.
Any help would be a great help. Thanks for your time.
Is there a Join function available?
Unfortunately, there is no JOIN query in Firestore. Queries in Firestore are shallow: they only get items from the collection that the query is run against. There is no way to get documents from a top-level collection and other collections or sub-collections in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection. So the most simple solution I can think of would be to use a database structure that looks similar to this:
Firestore-root
|
--- playlists (collection)
| |
| --- playListId (document) //The unique id of the play list
| |
| --- name: "playlist1"
|
--- songs (collection)
|
--- playListId (document) //The same as the above playListId
|
--- playListSongs
|
--- songId
|
--- title: "song1"
In order to display all playlists, just attach a listener to the playlists reference and get all playlist objects. If you want to get all songs that correspond to a particular playlist, just attach a listener to songs\playListId\playListSongs and get all song objects.
Regarding the count of all songs that correspond to a playlist, I recommend you see my answer from this post, where I have explained what you can achieve this. So according to the last part of my answer, your Firebase Realtime Database structure should look like this:
Firebase-Realtime-Database-root
|
--- playlists
|
--- playListIdOne: numberOfSongs
|
--- playListIdTwo: numberOfSongs
Edit:
I can't say I've understood it fully especially because the first answer involves Firestore and the second involves Firebase Realtime Database.
I gave you this solution because if you want to use Cloud Firestore to count and update elements every time a song is added or deleted, you'll be charged by Firestore for every write/delete operation. Firebase Realtime Database has another pricing plan, so you'll not be charged for that. See Firestore pricing Please read again till the end, my answer from this post.
Also, I couldn't really get the procedure for calculating the count of songs.
This is how you can get the number of songs from Firestore and write it to the Firebase Realtime Database:
rootRef.collection("songs").document(playListId).collection("playListSongs")
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
Log.d("TAG", task.getResult().size() + "");
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference playListIdRef = rootRef.child("playlists").child(playListId);
playListIdRef.setValue(task.getResult().size());
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
And this is how you can read:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference playListIdRef = rootRef.child("playlists").child(playListId);
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
long playListIdOne = dataSnapshot.getValue(String.Long);
Log.d(TAG, "playListIdOne: " + playListIdOne);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage());
}
};
playListIdRef.addListenerForSingleValueEvent(valueEventListener);
Secondly, as per your suggested structure, how would I get a list of all songs across all playlists?
In this case, you should create another collection, named allSongs. The additional collection in your database should look like this:
Firestore-root
|
--- allSongs
|
--- songId
|
--- //song details
This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database. It is for Firebase Realtime Database but same rules apply to Cloud Firestore.
Also, when you are duplicating data, there is one thing that you need to keep in mind. In the same way you are adding data, you need to maintain it. In other words, if you want to update/delete an item, you need to do it in every place that it exists.
Keeping in mind that one same song can be found in more than one playlists.
It doesn't matter because each song has a different id.
Related
I am developing an Android chat application in which I need to order the conversation details by the date. My firebase data structure is mentioned below.
Now I want to retrieve and show the data on the latest date on my RecyclerView from Firebase Realtime Database based on timestamp.
I have tried the following approaches.
final DatabaseReference nm =
FirebaseDatabase.getInstance().getReference("Transaction");
Query query = nm.orderByChild("Date").limitToFirst(5);
;
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
listData.clear();
if (dataSnapshot.exists()) {
for (DataSnapshot npsnapshot : dataSnapshot.getChildren()) {
Transaction ld = npsnapshot.getValue(Transaction.class);
listData.add(ld);
}
Tadapter = new TransactionAdapter(listData);
rv.setAdapter(Tadapter);
Log.d(TAG, "Total Count" + Tadapter.getItemCount());
}
}
}
I am developing an android chat application in which I need to order the conversation details by the date.
As I see in your screenshot, your Date property is of type String. This means that you cannot call:
.orderByChild("Date")
And expect to behave as it was a Timestamp. When you order String elements, the order that you get is always lexicographically. This means that Strings doesn't consider any sort of numeric values when sorting, especially when it comes to the dates, even if the dates contain numbers, as your first element does:
Date: "30/7/2021"
So using String values when querying your database it's not an option. However, I see you already have a Timestamp property. Maybe on that property, it was supposed to do the ordering. If that was not the case, I suggest you change the type of the Date property from String to Timestamp, as explained in my answer from the following post:
How to save the current date/time when I add new value to Firebase Realtime Database
Now I want to retrieve and show the data on the latest date on my RecyclerView
This means that most likely you need to reverse the order, meaning that all your transactions have to be displayed in your RecyclerView descending. In this case, there are a few options that you have, either on the server or on the client.
Assuming that you have changed the type of your Date property from String to Timestamp, then you can simply consider storing an inverted Timestamp value like this:
Firebase-root
|
--- transactions
|
--- 1
|
--- Date: 1627714194
|
--- invertedDate: -1627714194
See, the invertedDate property holds a negative value. Since by default, the elements are ordered ascending, to be able to order the transaction desecendiong, you should simply use:
Query query = nm.orderByChild("invertedDate").limitToFirst(5);
On the other hand, there are some workarounds that can be made to achieve the same thing on the client, as explained in my answer from the following post:
How to arrange firebase database data in ascending or descending order?
Query query = nm.orderByChild("Date").limitToFirst(5);
Firebase realtime database sorts in ascending order that means those 5 nodes that you'll receive will be the oldest.
I want to retrieve and show the data in latest date
Try using limitToLast instead which will return the last 5 documents after ordering the nodes by Date field i.e. the 5 latest nodes.
Query query = nm.orderByChild("Date").limitToLast(5);
You can read more about that at sorting and filtering data.
I am developing an Android chat application in which I need to order the conversation details by the date. My firebase data structure is mentioned below.
Now I want to retrieve and show the data on the latest date on my RecyclerView from Firebase Realtime Database based on timestamp.
I have tried the following approaches.
final DatabaseReference nm =
FirebaseDatabase.getInstance().getReference("Transaction");
Query query = nm.orderByChild("Date").limitToFirst(5);
;
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
listData.clear();
if (dataSnapshot.exists()) {
for (DataSnapshot npsnapshot : dataSnapshot.getChildren()) {
Transaction ld = npsnapshot.getValue(Transaction.class);
listData.add(ld);
}
Tadapter = new TransactionAdapter(listData);
rv.setAdapter(Tadapter);
Log.d(TAG, "Total Count" + Tadapter.getItemCount());
}
}
}
I am developing an android chat application in which I need to order the conversation details by the date.
As I see in your screenshot, your Date property is of type String. This means that you cannot call:
.orderByChild("Date")
And expect to behave as it was a Timestamp. When you order String elements, the order that you get is always lexicographically. This means that Strings doesn't consider any sort of numeric values when sorting, especially when it comes to the dates, even if the dates contain numbers, as your first element does:
Date: "30/7/2021"
So using String values when querying your database it's not an option. However, I see you already have a Timestamp property. Maybe on that property, it was supposed to do the ordering. If that was not the case, I suggest you change the type of the Date property from String to Timestamp, as explained in my answer from the following post:
How to save the current date/time when I add new value to Firebase Realtime Database
Now I want to retrieve and show the data on the latest date on my RecyclerView
This means that most likely you need to reverse the order, meaning that all your transactions have to be displayed in your RecyclerView descending. In this case, there are a few options that you have, either on the server or on the client.
Assuming that you have changed the type of your Date property from String to Timestamp, then you can simply consider storing an inverted Timestamp value like this:
Firebase-root
|
--- transactions
|
--- 1
|
--- Date: 1627714194
|
--- invertedDate: -1627714194
See, the invertedDate property holds a negative value. Since by default, the elements are ordered ascending, to be able to order the transaction desecendiong, you should simply use:
Query query = nm.orderByChild("invertedDate").limitToFirst(5);
On the other hand, there are some workarounds that can be made to achieve the same thing on the client, as explained in my answer from the following post:
How to arrange firebase database data in ascending or descending order?
Query query = nm.orderByChild("Date").limitToFirst(5);
Firebase realtime database sorts in ascending order that means those 5 nodes that you'll receive will be the oldest.
I want to retrieve and show the data in latest date
Try using limitToLast instead which will return the last 5 documents after ordering the nodes by Date field i.e. the 5 latest nodes.
Query query = nm.orderByChild("Date").limitToLast(5);
You can read more about that at sorting and filtering data.
Below is image representation of my firebase where user upload items under Electronics or Books category and also more categories. I want to build an activity with RecyclerView where I want to show the only item uploaded by a user. Since I'm using userid which is unique to push the details of the item inside a child, I can use userid to retrieve the items from child and display in firebase. But, how to search in each child from different category and show it in one RecyclerView.
My Code to Show the items from Electronics child is
dbreference = FirebaseDatabase.getInstance().getReference("Electronic").child("userid");
dbreference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
for (DataSnapshot data : snapshot.getChildren()) {
final Solditem b1 = data.getValue(SoldItem.class);
child_count++;
list.add(b1);
staggeredBooksAdapter.notifyDataSetChanged();
}
}
}
But I also would like to show the uploaded item by a user which equals to userid as key from Books in the same RecyclerView.
To delete an item from any category I use
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
Query sellerquery = ref.child("Electronic").child("userid");
sellerquery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot appleSnapshot: dataSnapshot.getChildren()) {
appleSnapshot.getRef().removeValue();
}
}
}
To delete all the records of a user-uploaded item which key-value equals to userid, I need to repeat above code.
Is there any best and short method to delete all the records from any category which key-value equals to userid.
Thanks in advance.
While structuring your data in Firebase, you really need to think about the structure of your data very carefully so that you can run efficient queries on them. For example, in your case, you should have structured your data as follows.
userid
- Electronics
- Camera
- Cooker
- Books
- A brief history of time
- Inferno
Hence you could run your queries in such a way that, you could have all the items under a certain userid at once.
If you really need the structure that you have now for other sets of queries that you are planning to do in your application, think about replicating your data as well. That is, you might consider having duplicate entries of your data (replication) in different structure so that you can perform efficient queries. For example, the current structure of your data is suitable for the following query.
Get all userid under a specific category.
I think you have got my point. If you do not want to change the structure of your data then I am afraid that you might have to get all the data in your application from firebase first. Then you have to loop through the data yourself to find the elements that you actually needed.
Hope that helps!
I am developing an app, there's a functionality to get all the contacts of the user and upload it to server. Here I'm using Firebase Firestore as my backend. I can able to get all contacts without duplicates. Can someone please help, in which document structure I need to save the contact details of the user? Please tell me appropriate format so that it wont take much space in Firestore.
And then..
Once all the contacts are saved, I must be able to get the name of a contact using number what user is entering. This is my requirement. in short I would like to know how to save 2500 contact details for Firestore and how to read single contact name with help of contact number what user types in the edittext. (note : I heard like we cant save 2500 contact details of the user in one shot, something like batch should be used)
I tried this below code but it is saving only first 800 contacts only.
`private void doUploadContactstoCloud() {
dialog.setCancelable(false);
dialog.setMessage("Please wait we are configuring your request ...");
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.show();
listContacts = new ContactPresenter(this).fetchAllContacts();
int j = listContacts.size();
Log.d("sizejhfg",j+"");
double size = listContacts.size();
double ind = 0;
Map<String, Object> contact = new HashMap<>();
// Get a new write batch
WriteBatch batch = db.batch();
for (Contact con : listContacts) {
for (int i = 0; i < con.numbers.size(); i++) {
String number = formatnum(con.numbers.get(i).number);
if (number != null) {
if (!number.equals("0") && number.length() < 5) {
Map<String, Object> contactnumber = new HashMap<>();
contactnumber.put("name", con.name);
contactnumber.put("number", number);
contact.put(number,contactnumber);
DocumentReference item = db.collection(tonumber).document(number);
batch.set(item,contactnumber);
ind++;
if(ind==500){
doCommit(batch);
batch=db.batch();
ind=0;
}
}
}
}
//dialog.setProgress((int) (ind / size) * 100);
}
if(ind>0){
doCommit(batch);
}
prefManager.setisContactUpload(true);
doDismissDialog();
}`
Please tell how I should save data in Firestore (structure)
please tell me to read single contact name with help of number
Please tell how I should save data in Firestore (structure)
If you are using authentication, a possible database schema for you app use-case might be:
Firestore-root
|
--- users (collection)
|
--- uid (document)
|
--- // user properties
|
--- contacts (subcollection)
|
--- contactId (document)
|
--- phoneNumber: 123456789
|
--- // other contact properties
To get all contacts a user has, simply attach a listener on contacts reference:
db.collection("users").document(uid).collection("contacts");
please tell me to read single contact name with help of number
To get the details of single contact having the phone number, please use the following query:
db.collection("users").document(uid).collection("contacts").whereEqualTo("phoneNumber", 123456789);
According to the official documentation regarding the maximum number of documents that can be added in single batch:
Each transaction or batch of writes can write to a maximum of 500 documents.
So you have to split your contacts in parts of 500 documents in order to be able to add all those contacts to Firestore.
Edit:
if I assume one user is having 4000 contacts in his phone (just mobile number and name*4000 = 8000 data from a single user).
That's incorrect, if you have only 2 properties (within a contact document), you'll only have 4000 write operations and not 8000 because both properties are apart of the same document. So you must write them together using a batch write or using a POJO class.
if the number of users become 5 million then it will be huge data that needs to be stored on in the Firestore.
That's correct but in the same time it means that you'll have a very successful application and you can afford all those write operations. 5 million believe me, it's much.
so I want a schema which must the best which should accommodate less space as possible
The problem is not about the space, the problem is about the number of reads and writes you perform. Evrything in Firestore is about the number of reads and writes.
I am expecting a very efficient way to save data. please help
In my optinion, the schema above can you help you solve the problem.
considering this still your answer is the best solution?
Considering your request from your question and comments, yes.
is it possible to ignore uid, contact id and can just have only these parameters
Only if you use Firebase realtime database, which is a different product. There are no collections and documents, just a JSON database structure.
I have the following schema and I am not sure how to model it in Firestore.
I will be having clients and invoices. Clients "has" invoices. I need to be able to do these 2 queries:
Show invoices that a client has
Update all invoices in the system (change a boolean attribute from true to false).
What would be the right way to model this? The first query is satisfied by having a collection of clients with subcollection of their invoices. But the second one is satisfied by having a collection of all invoices?
Any experienced help is appreciated
Thank you
I have another recommendation which involves you to create two top level collections like this:
Firestore-root
|
--- users (collection)
| |
| --- userId (documents)
| |
| --- //user details
|
--- invoices (collection)
|
--- invoiceId (documents)
|
--- yourBooleanProperty: true
|
--- userId: true
As you can see, the simplest way to achieve this, is to have a single collection named invoices that can hold as documents all the invoices in your database. Because a single invoice can belong only to a single user, you can have the userId as a property. To get all the invoices that correspond to a specific user, I recommend you to use the following query:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query query = rootRef.collection("invoices").whereEqualTo(userId, true);
And if you want to change the boolean property of all invoices from true to false at once, simply use the following query:
Query query = rootRef.collection("invoices").whereEqualTo(yourBooleanProperty, true);
Remember that Firestore uses a document-oriented NoSQL model, similar to MongoDB and CouchDB, which leads to fundamentally different data structuring decisions.
You can think this structure in a relational way, and you can achieve the same results.
And you already did that here
The first query is satisfied by having a collection of clients with
subcollection of their invoices.
so in order to solve your last requirement i would make a collection with all the invoices and then share with the clients invoice the current invoice id, so you will have inside your first query a subcollection with the ids to refer to your all invoices, this way you can refer to them and make any changes you want just querying the first collection.
let me just illustrates how they can connect