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.
Related
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?
I am using the following query:
db.collection("Example")
.whereEqualTo("UserId", currentUser.getUid())
.orderBy("timestamp", Query.Direction.ASCENDING)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()){
for (QueryDocumentSnapshot document : task.getResult()){
Log.i(TAG, "onComplete: " + document.get("UserId") + " => " + document.get("userText"));
userIdArrayList.add(document.get("UserId").toString());
userTextArrayList.add(document.get("userText").toString());
}
}else{
Log.i(TAG, "onComplete: Error getting documents", task.getException());
}
}
});
As the query is iterating through results, I would like it to add the results to an array. However, when I run the code and attempt to print the content of the array list, I am getting and IndexOutOfBounds exception despite me looking at userTextArrayList.get(0); which should contain a result. I have added the line Log.i to check if the code is running successfully and I can see in the Logcat that the data is being pulled from the database. For some reason this code isn't adding the results to the ArrayList.
I'm not sure what else to try and I am unable to find anywhere in the documentation that can assist me with where I have potentially gone wrong.
Please feel free to point me towards the documentation that can assist with the problem if providing the answer is too much bother. I appreciate any guidance anyone can provide.
Thanks!
Edit: I am getting an error on the following lines of code:
userIdArrayList.add(document.get("UserId").toString());
userTextArrayList.add(document.get("userText").toString())
For some reason these lines aren't adding the data to the array and when I attempt to print the array later in the code (i.e. Log.i(TAG, "User Text => userTextArrayList.get(0).toString());), I am recieving and error for IndexOutOfBounds - apparently the ArrayList does not contain data despite the above code adding data to the ArrayLists.
You need to understand that this query is asynchronous and the results might not have yet been added to userTextArrayList when you execute this log statement Log.i(TAG, "User Text => userTextArrayList.get(0).toString());
This log statement needs to be in onComplete() as below in order for it to work:
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.i(TAG, "onComplete: " + document.get("UserId") + " => " + document.get("userText"));
userIdArrayList.add(document.get("UserId").toString());
userTextArrayList.add(document.get("userText").toString());
}
yourButton.setEnabled(true);
} else {
Log.i(TAG, "onComplete: Error getting documents", task.getException());
yourButton.setEnabled(false);
}
}
I am coding an android app using Google's FireStore backend. The code to grab all the documents in a firestore collection is as follows, from the official documentation:
db.collection("cities")
.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());
}
}
});
Where the above code outputs to Log.d(...), I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method. I'm not sure what is the best way to do this. Attempting to change the return the return type of the onComplete method yields errors. Is there a standard way of accessing elements in methods like this?
Declaring a variable and trying to mutate within the class also isn't possible, unless the variable is final, which defeats the point.
That is an asynchronous call (it launches a background process to do the Firebase query, and once that is done it executes your onComplete listener), so you can't expect to have the data in hand immediately after making the database call. For example, if your function looks like
void getData() {
final List<MyData> list = new ArrayList<>();
db.collection("cities")
.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());
list.add(new MyData(document.getData()));
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Log.d(TAG, "List size = " + list.size()); // will print 0
// list will be empty here, the firebase call will take hundreds
// to thousands of milliseconds to complete
}
You need to structure your program so that it can wait for the data to arrive. There are a couple ways you could do this. One would be to have list be a class member that gets filled by the onComplete listener (then you have to structure the program to handle data coming in at random times).
Another way would be to have a data handler routine that takes the ArrayList and does something with it. This method could be called from the onComplete listener once you've gotten all the data. For example:
void getData() {
db.collection("cities")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<MyData> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
list.add(new MyData(document.getData()));
}
processData(list);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
void processData(List<MyData> data) {
// do stuff with the data, or make a copy, or otherwise share it
// with the rest of your program
}
I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method.
In this case, if you are using a model class, you should use toObject() method and add objects of type YourModelClass in an ArrayList like this:
if (task.isSuccessful()) {
List<YourModelClass> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
YourModelClass yourModelClass = document.toObject(YourModelClass.class);
list.add(yourModelClass);
//Do what you need to do with your list
}
}
As you can see, I advised you to use the list of YourModelClass objects inside the callback. This is because onComplete() method has an asynchronous behavior and you cannot simply use that list outside the callback because it will always be empty.
Attempting to change the return the return type of the onComplete method yields errors.
Changing the return type of the method will not give you errors but the result will always be an empty list. You cannot return something now that hasn't been loaded yet. A quick solve for this problem would be to use that list of objects only inside the onComplete() method, as I already wrote above, or if you want to use it outside, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
I have created a application using Firestore in this app I want to save a same string in all documents of a collection in one click
For Example: See in the image. I have created a collection name Links. In this Collection I have created many Documents.
So I want to save string field: name and value:anyname, in all documents in one click on press button.
How it's possible? Please help.
To achieve this, please use the following code:
CollectionReference linksRef = rootRef.collection("Links");
linksRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Map<String, Object> map = new HashMap<>();
map.put("propertyName", "propertyValue");
placesRef.document(document.getId()).update(map);
}
}
}
});
All your documents will have now a new propertyName property that will hold the value of propertyValue.
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>()
}