I can easily paginate a Recyclerview with the Firestore collection. But in my case, I have three dependent collections. They are
product (10000000 entries expected)
category (100000 entries expected)
region (50000 entries expected)
In Recyclerview I have to show details of product, category, and region. As we know, there is no option for inner join queries like SQL. I have category id and region id stored in product details. Based on the id, I need to filter and sort products. Also based on the region I need to change the currency in the product list. How can I achieve these kinds of complex architecture with Firestore to meet normal SQL features? In my case, Firestore is confirmed.
Product
{
"productId": 122,
"productName": "product name 1",
//other product details here
"categoryId": 758,
"regionId": 395
}
Category
{
"categoryId": 90474,
"categoryName": "category name 200",
//other category configuration details here
}
Region
{
"regionId": 2372,
"regionName": "tokyo",
//other region details here
}
To be able to perform a query based on a category and a region, you should first read their IDs. So assuming that a user selects "category name 200" and the "tokyo" for the region, in order to get the corresponding products, please use the following lines of code:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference categoryRef = rootRef.collection("Category");
CollectionReference regionRef = rootRef.collection("Region");
CollectionReference productRef = rootRef.collection("Product");
categoryRef.whereEqualTo("categoryName", "category name 200").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
String categoryId = document.getString("categoryId");
regionRef.whereEqualTo("regionName", "tokyo").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
String regionId = document.getString("regionId");
//Perform a query to get the products
productRef.whereEqualTo("categoryId", categoryId).whereEqualTo("regionId", regionId).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
String productName = document.getString("productName");
Log.d(TAG, productName);
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Be sure to have unique categories and regions, so that each query returns a single result.
If you need to order the results, you can also chain an orderBy() call, to order the products according to a specific property. Be also aware that an index is required for such a query.
Please also see below a simple solution to paginate the results in Firestore:
How to paginate Firestore with Android?
Related
I am working on an application where I have saved my data in firebase Firestore in nested collection now when I am trying to get/retrieve the data from Firestore but not able to get it. please guide me where am I wrong??
CODE TO WRITE/ADD THE DATA IN FIRESTORE
DocumentReference uidRef = firebaseFirestore.collection("listing_details").document(uid);
uidRef.collection("room_details").add(user).addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
Toast.makeText(getContext(), "data added", Toast.LENGTH_SHORT).show();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getContext(), "data adding failure", Toast.LENGTH_SHORT).show();
}
});
CODE FOR DATA RETRIEVING
db.collection("listing_details").document().collection("room_details").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
List<DocumentSnapshot> list = queryDocumentSnapshots.getDocuments();
for (DocumentSnapshot d : list)
{
RoomsDetails obj = d.toObject(RoomsDetails.class);
roomsDetails.add(obj);
}
roomsAdapter.notifyDataSetChanged();
}
});
DATA RETRIEVING CODE (UPDATED)
roomDetailsRef.document(doc_id).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//GUIDE ME HERE HOW CAN I ITERATE THROUGH IT SIR PLEASE
}
});
Each time you're calling .document() to create the following reference, without passing anything as an argument:
db.collection("listing_details").document().collection("room_details")
// 👆
It means that you're generating a brand new unique document ID. If you want to create a reference that points to a particular document, then you have to pass the particular document ID that already exists in the database to the document() method, and not generate a new one.
So your code should look like this:
//Code to add data to Firestore.
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference uidRef = db.collection("listing_details").document(uid);
CollectionReference roomDetailsRef = uidRef.collection("room_details");
String docId = roomDetailsRef.document().getId();
roomDetailsRef.document(docId).set(user).addOnSuccessListener(/*.../*);
// 👆
See, I have used DocumentReference#getId() to get the ID of the document, and DocumentReference#set(Object data) to actually add the document to Firestore.
//Code to read data from Firestore.
roomDetailsRef.document(docId).get().addOnSuccessListener(/*.../*);
// 👆
See, I have passed the document ID that was generated earlier, to the CollectionReference#document() method.
Edit:
If you want to get all documents that exist under the room_details collection, then please use the following lines of code:
roomDetailsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if (document != null) {
RoomsDetails obj = document.toObject(RoomsDetails.class);
roomsDetails.add(obj);
}
}
roomsAdapter.notifyDataSetChanged();
} else {
Log.d(TAG, task.getException().getMessage()); //Never ignore potential errors!
}
}
});
I have an Android App that has a recyclerView, where it shows the data returning from a query.
However, the query returns null when it should return something.
This is the query:
db.collection("Usuarios").whereEqualTo("email", mUser.getEmail()).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
//There can only be 1 document due to previus code.
DocumentSnapshot document = task.getResult().getDocuments().get(0);
ArrayList<String> listasDelUsuario = (ArrayList<String>) document.get("idsListasDeCompra");
//Query to get all "Listas de compra" docs where its field "id" is in the Array "listasDelUsuario" (I checked with Log and this array contains the id of all the user's listOfID
db.collection("Listas de Compra").whereIn("id", listasDelUsuario).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().isEmpty()) {
//It enters in this if, so the query is returning no data
}
}
}
});
}
}
});
Here is a screenshot with both collections so u can check the values im checking are the same.
Not sure why the query doesn't work, but in general I'd recommend accessing the list directly by its document ID, instead of querying for it:
db.collection("Listas de Compra").doc(listasDelUsuario).get()
enter image description here
/Info/Semestre1/Courses/course1 Is my full data base reference.
I'm trying to access all the courses name of all the semesters at ounce.
db.collection("Courses").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
}
// Log.d(TAG, document.getId() + " => " + document.getData());
}
}
});
}
I
I got all the semesters of Info. Now how can I access all the courses names?
You are doing the same mistake which i done when i was learning more about firestore.
So the first mistake you are doing is directly accessing the "Courses" collection which
is not possible if you want to access the "Courses" collection then you must need to traverse through all the collection and document before it.
Ex :collection1/doc1/collection2/doc2/collection3
Now if you want to access the data of the collection3 then query for that is going to be like
collection1.doc().collection2.doc2().collection3.get() and then your listner thats just basic understanding before we move for you answer.
Now ans of you question
db.collection("info").document("sem-id").collection(Courses).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
}
// Log.d(TAG, document.getId() + " => " + document.getData());
}
}
});
}
Let me know if still any doubt.Happy coding
I think your query in the question is actually incorrect. Based on what I see in your screenshot, to get all the semesters, you would do this:
db.collection("Info").get()
Then, to get all the courses for a specific semester in the subcollection nested under the semester, you would need a whole new query like this:
String courseId = ... // changes as you iterate the courses in Info
db.collection("Info").document(courseId).collection("Courses").get()
You can't get all the semesters and courses in one query with the database structure you have now. Firestore queries are shallow, meaning they don't consider subcollections at all. Each subcollection requires its own separate query.
I write to the Firestore after creating a user, as coded below:
userMap.put("email", user.getEmail());
userMap.put("display_name", user.getDisplayName());
userMap.put("user_id", user.getUid());
userMap.put("provider", user.getProviders());
mStoreBaseRef.collection(USERS).add(userMap);
When this user is written to the Firestore, a unique ID is generated for that User.
Later on, I want to write to the user node, however I do not have the unique key that was generated. I query the "Users" node based on a specific ID of a user so that I can write to that node, but I am unsure how to obtain the key for that specific user:
Query x = mStoreBaseRef.collection(USERS_LABEL).whereEqualTo("user_id", mPollCreatorID);
x.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (DocumentSnapshot d : task.getResult()){
User user = d.toObject(User.class);
Log.v("USER", user.getUser_id());
//I need to add the document here of the unique key
mStoreBaseRef.collection(USERS_LABEL).add(followersMap);
}
}
I am trying to obtain mAQGM9S.......from below
They ID of a document is available through DocumentSnapshot.getId(). So:
Query x = mStoreBaseRef.collection(USERS_LABEL).whereEqualTo("user_id", mPollCreatorID);
x.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (DocumentSnapshot d : task.getResult()){
Log.v("ID", d.getId());
User user = d.toObject(User.class);
Log.v("USER", user.getUser_id());
//I need to add the document here of the unique key
mStoreBaseRef.collection(USERS_LABEL).add(followersMap);
}
}
You are asking for the push id of that document. For that, you should use getId() method. Here is how you can do that :
Query x = mStoreBaseRef.collection(USERS_LABEL).whereEqualTo("user_id", mPollCreatorID);
x.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (DocumentSnapshot d : task.getResult()){
User user = d.toObject(User.class);
//Getting push id
String pushId = d.getId();
Log.v("USER", pushId);
//...
}
}
Try it and let us know if it's working or not.
My structure of Firestore database:
|
|=>root_collection
|
|=>doc1
|
|=>collection
|
|=>doc2
|
|=>collection
|
|=>doc3
|
|=>collection
Now I wanna get list of document from root_collection. There would be a list with following data {"doc1", "doc2", "doc3"}. I need it because I want to make a spinner and put these data in the spinner. Then a user would be choose some document and download it.
I try to use the code below:
firestore.collection("root_collection")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG,document.getId() + " => " + document.getData());
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
But the code works only then I have structure of data without collections in the documents. In other case there aren't any documents in QueryDocumentSnapshot.
Thanks!
To have a list that contains all the name of your documents within the root_collection, please use the following code:
firestore.collection("root_collection").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
list.add(document.getId());
}
Log.d(TAG, list.toString());
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
The result in your logcat will be:
[doc1, doc2, doc3]
Remember, this code will work, only if you'll have some properties within those documents, otherwise you'll end ut with an empty list.
You can call collection method to get documentin the root_collections then hold documents ID which will be used to get document's collection later.
create root collection object as:
data class RootCollection(
#DocumentId val id: String,
val sampleField: String?
) {
// empty constructor to allow deserialization
constructor(): this(null, null)
}
Then get the collection using FirebaseFirestore method collection as follows:
val querySnapshot = firestore.collection("root_collection")
.get()
.await()
if(!querySnapshot.isEmpty) {
Result.SUCCESS(querySnapshot.toObjects(RootCollection::class.java))
} else {
Result.ERROR(Exception("No data available"))
}
Then to get collection in the document call
firestore.collection("root_collection/$documentId/collection")
Note that I have use kotlin coroutines await method as describe in this link
The Result class am using to save state of returned data and handle errors.
sealed class Result<out T: Any> {
data class SUCCESS<out T: Any>(val data: T) : Result<T>()
data class ERROR(val e: Exception): Result<Nothing>()
object LOADING : Result<Nothing>()
}