0

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
     }
metamonkey
  • 427
  • 7
  • 33
  • You need to use a callback interface . or if you are using Kotlin coroutines you can do it by `suspendCoroutine`. – ADM Jun 04 '21 at 05:35
  • https://stackoverflow.com/questions/57330766/why-does-my-function-that-calls-an-api-return-an-empty-or-null-value – a_local_nobody Jun 04 '21 at 06:18
  • 1
    @a_local_nobody, OP understands why value is empty, he asks how to wait for his listener execution – Victor Cold Jun 04 '21 at 06:21
  • @VictorCold don't remember saying anything else, that's why it's a Q&A post to explain how to resolve it – a_local_nobody Jun 04 '21 at 06:35
  • There is no way you can return `docVal` as a result of a method. Firebase API is asynchronous. So please check the duplicate to see how can you solve this using a custom callback. You can also achieve that using Kotlin Coroutine as, as Kamal Nayan already mentioned in his comment. – Alex Mamo Jun 04 '21 at 06:41

2 Answers2

0

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()
Kamal Nayan
  • 1,635
  • 1
  • 5
  • 19
  • Can you elaborate a bit on the actual implementation? I'm having a lot of issues trying to implement this. – metamonkey Jun 04 '21 at 15:14
  • I have updated the answer – Kamal Nayan Jun 04 '21 at 21:16
  • Getting "Expecting member declaration" error regarding the last line --> }.await() – metamonkey Jun 07 '21 at 18:12
  • I tried this a few different ways. Still getting that "Expecting member declaration" error. I have never used a .await() after a function declaration like that. I added implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1' to my build.gradle and import kotlinx.coroutines.tasks.await in the script. Still same problems. – metamonkey Jun 08 '21 at 20:11
  • Kindly Share the log. – Kamal Nayan Jun 09 '21 at 06:23
  • [log](https://drive.google.com/file/d/1AlEJyRE7DfPctRjwKQfcq2XQy1XlbqLn/view?usp=sharing) – metamonkey Jun 09 '21 at 19:39
  • I have another idea. you can use higher-order functions. https://kotlinlang.org/docs/lambdas.html – Kamal Nayan Jun 10 '21 at 07:18
  • That doesn't seem like a worthwhile use of time if the suspend/await() approach you laid out above is actually legitimate. – metamonkey Jun 10 '21 at 15:53
  • Okay, I understand check the image provided in the below link, I have implemented with the await() -> https://drive.google.com/file/d/1suhGo1bw3jK_BeO7l7FqIqqFDFjHkcPZ/view?usp=sharing and I have highlighted the .await() which is at line -66 – Kamal Nayan Jun 11 '21 at 00:06
0

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.

Victor Cold
  • 603
  • 5
  • 15
  • Thank you for this. It's a good workaround. Hopefully I can get the above implementation from Kamal to work at some point. Until then I'll use this. – metamonkey Jun 09 '21 at 02:37