Firestore Pagination : StartAfter query with DocumentSnapshot not working - android

I have a list of offers/deals saved in my firestore database. I want to fetch the first 3 deals sorted by discount in desc order i.e the deals with highest discount comes first and then next highest and so on. I am fetching them in segments of 3.
I am trying to achieve the same using android code. So, that the first 3 elements with discount 55,44,28 should come first, later 27,27,21 and finally 19,4.
class ContentGenerationActivityV1 : AppCompatActivity() {
var lastDocument : DocumentSnapshot?= null
lateinit var filterQuery: Query
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_content_generation)
val doc = FirebaseFirestore.getInstance().collection("steal-deals").document("deals")
filterQuery = doc.collection(getTodayTimeStamp().toString())
.orderBy("discount", Query.Direction.DESCENDING)
filterFetchButton.setOnClickListener {
if(lastDocument == null){
fetchFirstFew()
} else {
fetchMore()
}
}
}
//Get first 3 elements
fun fetchFirstFew(){
Log.d("N/W CALL ::", "FIRST")
filterQuery.limit(3) .get()
.addOnSuccessListener { result ->
lastDocument = result.documents.last()
for (document in result) {
Log.d("DEAL :: ", "${document.id} => ${document.data}")
}
}
}
//Get the next 3 elements
fun fetchMore(){
Log.d("N/W CALL ::", "SECOND")
filterQuery.startAfter(lastDocument)
.limit(3).get()
.addOnSuccessListener { result ->
lastDocument = result.documents.last()
for (document in result) {
Log.d("DEAL :: ", "${document.id} => ${document.data}")
}
}
}
But it is always returning the first three elements (55,44,28) no matter what.
Please guide me so in achieving the same.

I had the same problem but after some research found out the "startAfter(lastDocument)" returns a new Query instance which you need to get and on that you need to call limit.
val query = filterQuery.startAfter(lastDocument)
query.limit(3).get().addOnSuccessListener {}

i just got this issue.
reference
so, in your code
Wrong:
filterQuery.limit(3) .get()
.addOnSuccessListener { result ->
lastDocument = result.documents.last()
for (document in result) {
Log.d("DEAL :: ", "${document.id} => ${document.data}")
}
}
True:
filterQuery = filterQuery.limit(3) .get()
.addOnSuccessListener { result ->
lastDocument = result.documents.last()
for (document in result) {
Log.d("DEAL :: ", "${document.id} => ${document.data}")
}
}
because when you chaining to query, it creates and returns a new Query that starts after the provided document (exclusive). so you have to reassign it!

In addition to storing query object in separate reference, you also need to make sure that you are passing the non-null lastDocument to startAfter. You can either cast it to as Document Snapshot or put !! at the variable end
filterQuery.startAfter(lastDocument!!)
.limit(3).get()
OR
filterQuery.startAfter(lastDocument as DocumentSnapshot)
.limit(3).get()
It will surely work

Related

Read data from Firestore Jetpack Compose

I am currently trying to read data from Cloud Firestore, but while doing this I get this error message:
For-loop range must have an 'iterator()' method
And I don't know what to do to get rid of it. Maybe there is an easy way to fix this I haven't thought of yet...
I have been using this google firebase tutorial but with no success.
The error message comes on the documents in the for (document in documents) part of the code
The code I am using is this:
`
fun Varer() {
var firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
var docRef = firestore.collection("varer").document("varer")
var source = Source.DEFAULT
docRef.get(source).addOnSuccessListener { documents ->
for (document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
var v1 = VareFB(
tittel = document["tittel"].toString(),
pris = document.getDouble("pris"),
beskrivelse = document["beskrivelse"].toString(),
bildeID = document["bildeID"].toString(),
)
varerListe.add(v1)
Log.d(TAG, document["tittel"].toString())
Log.d(TAG, v1.toString())
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Feil med henting av varer: ", exception)
}
}
`
data class VareFB (
val tittel: String,
val pris: Double?,
val beskrivelse: String,
val bildeID: String,
) {
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"tittel" to tittel,
"pris" to pris,
"beskrivelse" to beskrivelse,
"bildeID" to bildeID,
)
}
}
`
object VarerObject {
var varerListe = mutableListOf<VareFB>()
}
`
Edit:
fun Varer() {
var firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
var docRef = firestore.collection("varer").document("varer")
var source = Source.DEFAULT
docRef.get(source).addOnSuccessListener { snapshot ->
for (document in snapshot.documents) {
Log.d(TAG, "${document.id} => ${document.data}")
var v1 = VareFB(
tittel = document["tittel"].toString(),
pris = document.getDouble("pris"),
beskrivelse = document["beskrivelse"].toString(),
bildeID = document["bildeID"].toString(),
)
varerListe.add(v1)
Log.d(TAG, document["tittel"].toString())
Log.d(TAG, v1.toString())
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Feil med henting av varer: ", exception)
}
}
documents is a QuerySnapshot object so there is no way you can iterate over it. To be able to iterate through the documents, you have to get the documents out of the QuerySnapshot object like this:
firestore.collection("varer").get(source).addOnSuccessListener { documents ->
for (document in documents.documents) {
//
}
}
But in my opinion, it's a little confusing. So I would rather name the object that comes from the lambda expression snapshot:
// 👇
firestore.collection("varer").get(source).addOnSuccessListener { snapshot ->
for (document in snapshot.documents) {
// 👆
}
}

Kotlin: Make the function wait to return until for loop is complete [duplicate]

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 1 year ago.
I have a simple function that retrieves a value from Firebase Firestore. I want to return the value. How do I make the function wait until the value is retrieved, rather than immediately returning an empty value?
Please Kotlin only.
fun fireStoreGetter(keyVal: String): String {
Log.d("FIRESTORE_OP", "fGet running with keyVal: " + keyVal)
//logs: FIRESTORE_OP: fGet running with keyVal: exampleString
var userId :String = ""
var mFirebaseDatabaseInstances = FirebaseFirestore.getInstance()
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
userId = user.uid
//Log.e(TAG, "User data is null")
} else {
Log.d("FIRESTORE_OP", "MUST AUTHENTICATE TO ACCESS FIRESTORE")
}
Log.d("FIRESTORE_OP", "USER IN WITH UID: " + userId)
//logs FIRESTORE_OP: USER IN WITH UID: DGGiDibnldhP5z6iUyf_GQ
val docRef2 = mFirebaseDatabaseInstances.collection("users").document(userId).collection("dBase").document("exampleDoc")
docRef2.get()
.addOnSuccessListener { document ->
//for (document in result) {
if (document != null) {
//docVal is instantiated in main thread
docVal = document.getString(keyVal).toString()//String(keyVal)
Log.d("FIRESTORE_OP", "DOCVAL SET: " + docVal)
//logs: FIRESTORE_OP: DOCVAL SET: (correct value from firestore)
}
}
Log.d("FIRESTORE_OP", "RETURNING fGet: " + "VALUE: " + docVal)
//logs: FIRESTORE_OP: RETURNING fGet: VALUE:
return docVal
//returns empty
}
So, this is how you try to do it now:
fun myFunction() {
val value = fireStoreGetter(keyVal)
...
}
fun fireStoreGetter(keyVal: String): String {
val docRef2 = ...
docRef2.get().addOnSuccessListener { document ->
for (document in result) {
if (document != null) {
docVal = ...
}
}
}
return docVal
}
And this is how you should do it:
fun myFunction(value: String) {
...
}
fun fireStoreGetter(keyVal: String) {
val docRef2 = ...
docRef2.get().addOnSuccessListener { document ->
for (document in result) {
if (document != null) {
docVal = ...
}
}
myFunction(docVal)
}
}
Your docRef2.get() returns a Task instance. Which hasn't functions to wait for its result right in place, like in a suspend coroutine function or something like that. So the only way is not to return the result from fireStoreGetter function, but rather trigger myFunction when the result is ready.
UPD: Turns out there IS a way to wait for Task's result right in place. See Kamal's answer if you really need to get rid of callbacks. But keep in mind that this will require a little extra tweaking. Coroutines, suspend functions, etc.
You need to add below dependency to use await()
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
and use suspend keyword in the function name and add .await() after success or failure listener of firebase. This will make the code wait for the function to retrieve data from firebase.
You can read this blog for reference
Your code should be like this:
suspend fun fireStoreGetter(keyVal: String): String {
Log.d("FIRESTORE_OP", "fGet running with keyVal: " + keyVal)
//logs: FIRESTORE_OP: fGet running with keyVal: exampleString
var userId :String = ""
var mFirebaseDatabaseInstances = FirebaseFirestore.getInstance()
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
userId = user.uid
//Log.e(TAG, "User data is null")
} else {
Log.d("FIRESTORE_OP", "MUST AUTHENTICATE TO ACCESS FIRESTORE")
}
Log.d("FIRESTORE_OP", "USER IN WITH UID: " + userId)
//logs FIRESTORE_OP: USER IN WITH UID: DGGiDibnldhP5z6iUyf_GQ
val docRef2 = mFirebaseDatabaseInstances.collection("users").document(userId).collection("dBase").document("exampleDoc")
docRef2.get()
.addOnSuccessListener { document ->
//for (document in result) {
if (document != null) {
//docVal is instantiated in main thread
docVal = document.getString(keyVal).toString()
}
}
return docVal
}.await()

why I always get empty documents when using pagination in Firestore

I am trying to use pagination by using document snapshot to define the query cursor.
when to fragment is opened for the first time, in onCreateView I use the code below to get 7 events from firestore
fun getAllSearchedEventsFromTheBeginning(startDate: Date, endDate: Date, selectedCity: String, selectedEventType: String, limit: Long, completion: (errorMessage:String?, events: ArrayList<Event>?, lastDocument: DocumentSnapshot?) -> Unit) {
// not only free events, paid events are also included
FirestoreCollectionReference.event.getReference()
.whereEqualTo(FIRESTORE_EVENT_FIELD_CITY,selectedCity)
.whereEqualTo(FIRESTORE_EVENT_FIELD_EVENT_TYPE, selectedEventType)
.whereEqualTo(FIRESTORE_EVENT_FIELD_HAS_BEEN_APPROVED,true)
.whereGreaterThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,startDate)
.whereLessThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,endDate)
.orderBy(FIRESTORE_EVENT_FIELD_DATE_START_TIME, Query.Direction.ASCENDING)
.limit(limit)
.get()
.addOnSuccessListener { snapshot ->
val lastDocument = snapshot.documents[snapshot.size() - 1]
val eventDocuments = snapshot.documents
val eventArray = ArrayList<Event>()
for (document in eventDocuments) {
val eventData = document.data
val event = Event(dataEvent = eventData)
eventArray.add(event)
}
completion(null,eventArray, lastDocument)
}.addOnFailureListener {
completion(it.localizedMessage,null,null)
}
}
I am using lamda expression, to send the lastVisible document, and that lastVisible document will be used as the starting point for my next query
after reaching the bottom of my recycler view, then I use the code below to get the next 7 documents from firestore
fun getAllSearchedEventsAfterLastDocument(startDate: Date, endDate: Date, selectedCity: String, selectedEventType: String, limit: Long, lastDocument: DocumentSnapshot?, completion: (errorMessage:String?, events: ArrayList<Event>?, lastDocument: DocumentSnapshot?) -> Unit) {
// not only free events, paid events are also included
FirestoreCollectionReference.event.getReference()
.whereEqualTo(FIRESTORE_EVENT_FIELD_CITY,selectedCity)
.whereEqualTo(FIRESTORE_EVENT_FIELD_EVENT_TYPE, selectedEventType)
.whereEqualTo(FIRESTORE_EVENT_FIELD_HAS_BEEN_APPROVED,true)
.whereGreaterThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,startDate)
.whereLessThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,endDate)
.orderBy(FIRESTORE_EVENT_FIELD_DATE_START_TIME, Query.Direction.ASCENDING)
.limit(limit)
.startAfter(lastDocument)
.get()
.addOnSuccessListener { snapshot ->
val eventDocuments = snapshot.documents
if (eventDocuments.isEmpty()) {
completion("Event is empty",null, null)
} else {
val lastDocument = snapshot.documents.last()
val eventArray = ArrayList<Event>()
for (document in eventDocuments) {
val eventData = document.data
val event = Event(dataEvent = eventData)
eventArray.add(event)
}
completion(null,eventArray, lastDocument)
}
}.addOnFailureListener {
completion(it.localizedMessage,null,null)
}
}
I am sure that I send the same parameters to both of those function,
and the last documents is also correct, it exactly the same as the last document that appears in my recycler view.
but I always get empty documents if called that second function getAllSearchedEventsAfterLastDocument
and this line below always triggered in getAllSearchedEventsAfterLastDocument.
if (eventDocuments.isEmpty()) {
completion("Event is empty",null, null)
}
please help me, I am confused.
I finally find the problem,
in .startAfter(lastDocument)
that lastDocument still in nullable type (DocumentSnapshot?), it shouldn't.

how to get data from object _field in firestore

i need to get data from object (named "0") in a document in firestore, is that possible ?
this is my code now:
val db = FirebaseFirestore.getInstance()
val docRef = db.collection("accessories")
.document("brand0")
docRef.get().addOnCompleteListener(OnCompleteListener<DocumentSnapshot> { task ->
if (task.isSuccessful) {
val document = task.result
val group = document.get("0") as ArrayList<String>
}
but casting Any to Arraylist is not possible, any other way to get these data ?
It looks like 0 is a an object type field. That means it'll be represented locally as a Map type object with strings as the keys for the properties it contains.
After some trials and errors this is what worked for me in the end, but I am not sure why. I am using kotlin in my current project.
fun getOwner(userId: String) {
val db = Firebase.firestore
val docRef = db.collection("users").document(userId)
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
Log.d(TAG, "DocumentSnapshot data: ${document.data}")
val data = document.data as Map<String, String>
showOwnerName.text = data["name"]
} else {
Log.d(TAG, "No such document")
}
}
.addOnFailureListener { exception ->
Log.d(TAG, "get failed with ", exception)
}
}
I am converting the data I am getting to a map and this is how I am able to access its values.

How to replace AsyncTask with doAsync

I am retriving all documents from firestore and storing it in a ArrayList with a each element being a custom dataType. The data retriving process takes a little time and so I want to make the listView after the data has been retrived. I have used AsyncTask before and would have used the onPostExectue function but I came to know about doAsync in kotlin and wanted to gitve it a try.
Can someone guide me how to do it?
This is the function to get Data
fun initFirestore(): ArrayList<MetaData>{
FirebaseApp.initializeApp(this#MainActivity)
val db = FirebaseFirestore.getInstance()
val returnData: ArrayList<MetaData> = ArrayList()
db.collection("name_of_collection")
.orderBy("id")
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
val data = document.data
returnData.add(MetaData(data["name"].toString(), data["artist"].toString(), data["url"].toString()))
// Log.d("Test", document.id + " => " + data)
}
} else {
// Log.d("Test", "Error getting documents: ", task.exception)
}
}
return returnData
}
The log shows the correct data. And I am calling it from another function
fun getSongs(){
doAsync {
val test = initFirestore()
onComplete {
Log.v("Test","$test")
}
uiThread {
Log.v("A","$test")
}
}
}
Both log here return empty list

Categories

Resources