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
Related
I am using the Firestore database and need to fetch some data from it. How do I make the function await for the data before returning the list?
fun getExercise(bodyPart: String): MutableList<String> {
val db = Firebase.firestore
val exercisesList = mutableListOf<String>()
val exercise = db.collection("exercises")
val query = exercise.whereEqualTo("body-part", bodyPart)
query.get().addOnSuccessListener { result ->
for(temp in result) {
exercisesList.add(temp.id)
Log.d(TAG, "${temp.id} => ${temp.data}")
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
return exercisesList
}
I know I need to use .await() but I am new to Kotlin and can't make it work.
I see 2 possible options:
1. Change your code in order to call another function with the result instead of returning the result
fun getExercise(bodyPart: String) {
val db = Firebase.firestore
val exercisesList = mutableListOf<String>()
val exercise = db.collection("exercises")
val query = exercise.whereEqualTo("body-part", bodyPart)
query.get().addOnSuccessListener { result ->
for(temp in result) {
exercisesList.add(temp.id)
Log.d(TAG, "${temp.id} => ${temp.data}")
}
// Call another function with the result:
anotherFunction(exercisesList)
}.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
}
2. Implement Kotlin Coroutines
This option might be a little more complex than the first one for someone who's new to the language, as you'd need to understand the concept of Kotlin Coroutines.
Start by adding the Coroutines dependency to your build.gradle file:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
Change your function to become a suspend function:
suspend fun getExercise(bodyPart: String): MutableList<String> {
// ...
}
Use the await() extension function to fetch the result:
suspend fun getExercise(bodyPart: String): MutableList<String> {
val db = Firebase.firestore
val exercisesList = mutableListOf<String>()
val exercise = db.collection("exercises")
val query = exercise.whereEqualTo("body-part", bodyPart)
try {
val result = query.get().await()
for (temp in result) {
exercisesList.add(temp.id)
Log.d(TAG, "${temp.id} => ${temp.data}")
}
return exercisesList
} catch (e: Exception) {
Log.w(TAG, "Error getting documents: ", exception)
return emptyList() // returning an empty list in case the fetch fails
}
}
Use a Coroutine scope when making a call to your suspend function:
// If you're calling from an Activity/Fragment, you can use the
// lifecycleScope from the lifecycle-runtime-ktx library
//
// If you're calling from a ViewModel, consider using the
// viewModelScope from the lifecycle-viewmodel-ktx library
//
// See https://d.android.com/topic/libraries/architecture/coroutines#dependencies
// for more details
lifecycleScope.launch { // Coroutine Scope
val exercises: MutableList<String> = getExercise("arms")
// use the list
}
More resources:
Coroutines on Android
How to use Kotlin Coroutines with Firebase APIs Youtube Short
This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Using .addOnSuccessListener to return a value for a private method
(2 answers)
Closed 12 months ago.
I am new to Firestore / Document DB / NoSQL, so please be patient with me on this.
I have something like below where a document is created in the "Users" collection when user sign in for the first time
class FirestoreService{
private val db = Firebase.firestore
private var userExists:Int = 0
private var documentRef = ""
fun addUser(user: User) {
// check if the user exists
db.collection("Users")
.whereEqualTo("Email", user.email).get()
.addOnSuccessListener { documents ->
// async, so following variables will not be initialized when
// synchronous code is being called
for (document in documents) {
documentRef = document.id
userExists = if(docRef!=null) 1 else 0
}
}
.addOnFailureListener { e ->
Log.w(TAG, "Error adding User document", e)
}
if (userExists == 0){
val userHashMap = hashMapOf(
"name" to user.name,
"email" to user.email,
"notif" to false
)
db.collection("Users")
.add(userHashMap)
.addOnSuccessListener { documentReference ->
Log.d(TAG, "User document added!")
Log.d(TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
}
.addOnFailureListener { e ->
Log.w(TAG, "Error adding User document", e)
}
}
}
fun updateUser(user:User){
db.collection("Users")
.document(documentRef)
.set({
"notif" to user.settings?.notifOn
})
.addOnSuccessListener { Log.d(TAG, "User DocumentSnapshot successfully updated!") }
.addOnFailureListener { e -> Log.w(TAG, "Error updating User document", e) }
}
}
Inside a fragment
// inside fragment method
val firestoreService = FirestoreService()
firestoreService.addUser(user);
// inside another fragment method
firestoreService.updateUser(user2)
As you can see I am setting variables inside addOnSuccessListener which is asynchronous so the synchronous if condition and updateUser calls do not work as expected (required values may not be assigned to the userExists, documentRef when synchrnous code being called). As I know these async behavior is handled using callbacks like mentioned in here. But I am not sure how to make it work in my case with addOnSuccessListener?
I have written a code to fetch data from Cloud Firestore and am trying to implement the network calls using coroutines. I have tried to follow the official guides as much as possible, but since the functions have been left incomplete in those docs, I have made adjustments according to my requirements, but those might be the problem itself.
Here's the function which fetches the data:
suspend fun fetchHubList(): MutableLiveData<ArrayList<HubModel>> = withContext(Dispatchers.IO) {
val hubList = ArrayList<HubModel>()
val liveHubData = MutableLiveData<ArrayList<HubModel>>()
hubsListCollection.get().addOnSuccessListener { collection ->
if (collection != null) {
Log.d(TAG, "Data fetch successful!")
for (document in collection) {
Log.d(TAG, "the document id is ")
hubList.add(document.toObject(HubModel::class.java))
}
} else {
Log.d(TAG, "No such document")
}
}.addOnFailureListener { exception ->
Log.d(TAG, "get failed with ", exception)
}
if (hubList.isEmpty()) {
Log.d(TAG, "Collection size 0")
} else {
Log.d(TAG, "Collection size not 0")
}
liveHubData.postValue(hubList)
return#withContext liveHubData
}
And here is the ViewModel class which is calling this method:
class HubListViewModel(application: Application): AndroidViewModel(application) {
// The data which will be observed
var hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch (Dispatchers.IO){
hubList = hubListDao.fetchHubList()
Log.d(TAG, "Array List fetched")
}
}
}
Using the tag messages I know that an empty list is being returned, which I know from another question of mine, is because the returned ArrayList is not in sync with the fetching operation, but I don't know why, since I've wrapped the whole function inside a with context block. Please tell me why the return and fetching is not being performed sequentially.
you should add this dependency "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3". It allows you to use await() to replace callbacks.
suspend fun fetchHubList(): List<HubModel>? = try {
hubsListCollection.get().await().map { document ->
Log.d(TAG, "the document id is ${document.id}")
document.toObject(HubModel::class.java)
}.apply {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "Collection size is $size")
}
} catch (e: Exception) {
Log.d(TAG, "get failed with ", e)
null
}
Dispatchers.IO is not necessary since firebase APIs are main-safe
class HubListViewModel(application: Application): AndroidViewModel(application) {
val hubList = MutableLiveData<List<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
Log.d(TAG, "List fetched")
}
}
}
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
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.