What counts as a read in Firestore:
db.collection("Collection").document(id).get()
OR:
db.collection("Collection").document(id).get().addOnCompleteListener { task ->
task.result.get("field")
task.result.get("field")
task.result.get("field")
task.result.get("field")
}
Both. You are calling .get() method so that makes a call to Firestore servers (if offline persistence is not enabled or document is not found in cache). Just creating the DocumentReference however does not charge:
// just a reference, document data not fetched
db.collection("Collection").document(id)
In the 2nd code snippet, the task.result seems to be fetched data and you are just read a single field locally.
Related
There was a question about loading data from Firebase Firestore Cloud.
There is a simple query:
val firestore = Firebase.firestore
suspend fun getDocuments(timestamp: Timestamp): List<Entity> =
firestore
.collection("collection")
.orderBy("modified_at", Query.Direction.ASCENDING)
.whereGreaterThan("modified_at", timestamp)
.get()
.await()
.toObjects(Entity::class.java)
What happens if something goes wrong while uploading documents? Will I receive incomplete data or an error?
In what order are the documents loaded? Does orderBy affect the load order?
What happens if something goes wrong while uploading documents?
As far as I can see, you aren't uploading any documents, you're only reading them using get() which:
Executes the query and returns the results as a QuerySnapshot.
When it comes to performing CRUD operations in Firestore, there is always the possibility that an operation can fail, for example, due to improper security rules. That being said, it's always recommended to handle errors. So your method should contain a try-catch as sown below:
suspend fun getDocuments(timestamp: Timestamp): List<Entity> {
return try {
firestore
.collection("collection")
.orderBy("modified_at", Query.Direction.ASCENDING)
.whereGreaterThan("modified_at", timestamp)
.get()
.await()
.toObjects(Entity::class.java)
} catch (e: Exception) {
throw e
}
}
However, a more elegant solution would be to use a class that contains two fields, the data, and the exception. This practice is explained in the following resource:
How to read data from Cloud Firestore using get()?
In what order are the documents loaded?
You get the documents ordered according to what you pass as an argument to the orderBy() method.
Does orderBy affect the load order?
For sure, that's why we are using it, to have a particular order.
I am trying to implement a clean way of notifying my UI that there is no data. It's more like a listener that check that there is network connection but no data at that location. How do I do this?
I have tried 998 ways :(, they all work but the complexity of implementing them in every class that listens for data is giving me nausea.
Thanks for your reply.
You can simply check for the snapshot object whether there is data at that particular reference node or not like this:
anyRef.get().addOnSuccessListener(snapshot -> {
if (snapshot.exists()) {
// data exists
} else {
// data doesn't exist
}
}
Because database fetches usually happen asynchronously by default, a variable that holds the data from the firebase database fetch will be null when used right after the fetch. To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries. People also call the succeeding code from within 'addOnSuccessListener{}' but this seems to go against the purpose of MVVM, since 'addOnSuccessListener{}' will be called in the model part of MVVM, and the succeeding code that uses the fetched data will be in the ViewModel. The answer I'm looking for is maybe a listener or observer that is activated when the variable (whose value is filled from the fetched data) is given a value.
Edit:
by "succeeding code" I mean what happens after the database fetch using the fetched data.
As #FrankvanPuffelen already mentioned in his comment, that's what the listener does. When the operation for reading the data completes the listener fires. That means you know if you got the data or the operation was rejected by the Firebase servers due to improper security rules.
To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries.
It doesn't. Using ".await()" is indeed an asynchronous programming technique that can help us prevent our applications from blocking. When it comes to the MVVM architecture pattern, the operation for reading the data should be done in the repository class. Since reading the data is an asynchronous operation, we need to create a suspend function. Assuming that we want to read documents that exist in a collection called "products", the following function is needed:
suspend fun getProductsFirestore(): List<Product> {
var products = listOf<Product>()
try {
products = productsRef.get().await().documents.mapNotNull { snapShot ->
snapShot.toObject(Product::class.java)
}
} catch (e: Exception) {
Log.d("TAG", e.message!!)
}
return products
}
This method can be called from within the ViewModel class:
val productsLiveData = liveData(Dispatchers.IO) {
emit(repository.getProductsFromFirestore())
}
So it can be observed in activity/fragment class:
private fun getProducts() {
viewModel.producsLiveData.observe(this, {
print(it)
//Do what you need to do with the product list
})
}
I have even written an article in which I have explained four ways in which you can read the data from Cloud Firestore:
How to read data from Cloud Firestore using get()?
again :) I got a question about Cloud Firestore and Kotlin.
I need to get data from firestore with some code like this:
{
val comments = mutableListOf<Comment>()
val firestore = FirebaseFirestore.getInstance()
firestore.collection(collection).document(documentID).collection("comments")
.addSnapshotListener { querySnapshot, firebaseFirestoreException ->
comments = querySnapshot?.toObjects(Comment::class.java)!!
// do something with 'comments'. Works: comments is populated
}
// do something with variable 'comments'. Doesn't work: comments is now empty
}
The variable 'comments' gets populated inside the listener curly brackets but when the listener ends, the value goes back to 0.
I've researched online and found examples in JAVA that works perfectly this way, for example:
https://youtu.be/691K6NPp2Y8?t=246
My purpose is to fetch data only ONCE from the Cloud Firestore and store that value in a global variable, comments.
Please, let me know if you have a solution for this.
Thank you.
The value doesn't "go back to zero". You should understand that the database query is asynchronous, and addSnapshotListener returns immediately, before the query completes. The final value is only known when the listener is invoked some time later.
Also, you should know that if you just want to query a single time, you should use get() instead of addSnapshotListener(). It is also asynchronous and returns immediately, and the Task it returns will get invoked some time later. There are no synchronous options that block the caller until the query is complete - you will need to learn how to do your work asynchronously.
I am looking for a solution for my problem, i have a chat app which displays all the users correspondence as a Recyclerview a listener, in-order to improve user experience when a user send a message i manually update the Recyclerview with the current list plus the new message with an "uploading indication" then when the message actually uploads to Firebase the listener overrides the local list i used and updates the last message to "uploaded", problem is if i rebuild the activity the listener automatically update the Recyclerview's list to the one cached therefore the user cannot see his "uploading" message any more until it hits the server.
is was thinking if there is a manually way i can set the listener's cache to the local list without waiting for it to upload? if not maybe there is a better solution i hadn't thought about? (i have many different chats with many different users so i need to be able to fetch every chat channels own unique list)
There is an option in which you can force a query to retrieve data only from the cache. If this is what you need, you can achieve this with the help of the DocumentReference.get(Source source) and Query.get(Source source) methods.
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
So you can pass as an argument to the DocumentReference or to the Query the source so we can force the retrieval of data from the server only, chache only or attempt server and fall back to the cache.
So something like this will do the trick:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docIdRef = db.collection("tests").document("fOpCiqmUjAzjnZimjd5c");
docIdRef.get(Source.CACHE).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//Get data from the documentSnapshot object
}
});
In this case, we force the data to be retrieved from the cache only. If you want to force the data to be retrieved from the server only, you should pass as an argument to the get() method, Source.SERVER. More informations here.