2

I'm trying to check whether a document in the firestore exists. I copied an exact location of the document (col3) from the firestore console, so it must correct.

The document.exists() returns false despite the document being saved in the database. I followed Google guide from this site.

I've set the break point and checked the DocumentSnapshot object, but it very hard to follow e.g zza, zzb, zzc...

private fun nameExists(userId: String, colName: String): Boolean{
    val nameExists = booleanArrayOf(false)
    val docRefA = fbDb!!.document("users/X9ogJzjJyOgGBV0kmzk7brcQXhz1/finalGrades/col3")

    val docRefB = fbDb!!.collection("users")
            .document(userId)
            .collection("finalGrades")
            .document(colName)


    docRefA.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val document = task.result
            if (document.exists()) {
                nameExists[0] = true
            }else{
                Log.d(TAG, "no such document")
            }
        } else {
            Log.d(TAG, "get failed with ", task.exception)
        }
    }
    return nameExists[0]
}

enter image description here

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Pitos
  • 659
  • 6
  • 23
  • Can you show a screenshot of the Firebase console with the document you're trying to load and its compete path? – Frank van Puffelen Oct 04 '18 at 04:24
  • @Frank van Puffelen - Just to clarify, I'm trying to get a document, not load. However, I don't care about document.getData(); I'm only interested in returning boolean from the document.exists(); to inform a user that they should save the new document under a different name. [screenshot](https://drive.google.com/file/d/13JEnEWM2D1CScB78yY0aUbacc0j9tz9k/view) from firestore. – Pitos Oct 04 '18 at 08:48
  • Calling `get()` loads the document data. – Frank van Puffelen Oct 04 '18 at 14:14
  • The Logcat entries suggest that the operation to get DocumentReference is run asynchronously, so despite the fact that the value in nameExists[0] is captured in the closure, it is updated after it is returned by the nameExists method. 10-12 00:02:09.190 20519-20519/com.proto.patryk.ou_gradecalculator D/ModulesFragment: **returning value false** ... 10-12 00:02:11.715 20519-20519/com.proto.patryk.ou_gradecalculator D/ModulesFragment: **setting true on name exists** Is it possible to return the boolean from the lambda function? – Pitos Oct 12 '18 at 00:19
  • 1
    Oh sorry, I completely missed that. There is indeed no way that `return nameExists[0]` will work, since the callback black hasn't run yet. That's precisely the reason `get()` uses a completion listener itself to signal that the data is available. If you need to ensure you have data, you will need to pass in a completion listener too (see [here](https://stackoverflow.com/questions/50434836/getcontactsfromfirebase-method-return-an-empty-list/50435519#50435519) for another Firebase database, but the same problem and solution), or return a `Task` yourself. – Frank van Puffelen Oct 12 '18 at 01:34

1 Answers1

0

Thanks to @Frank van Puffelen hints, I was able to debug the code. From what I researched, the following code is a classic approach to solve this type of problem. Perhaps someone will find it useful.

Step 1. Define a functional interface with a parameter of the same type as the primitive value or object you want to return from the asynchronous operation.

interface OnMetaReturnedListener{
    fun onMetaReturned(colExists: Boolean)
}

Step 2. Create a method passing an interface reference as an argument. This method will be running the synchronous operation. Once the value is retrieved, call the functional interface method and pass the retrieved value/object as the method argument.

private fun nameExists(metaReturnedListener: OnMetaReturnedListener, colName: String){

    val userId = fbAuth!!.uid!!

    val docRefB: DocumentReference = fbDb!!.collection("users")
            .document(userId)
            .collection("finalGrades")
            .document(colName)

    docRefB.get().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val doc: DocumentSnapshot = task.result!!
            val colExists = doc.exists()

            metaReturnedListener.onMetaReturned(colExists)
        } else {
            Log.d(TAG, "get failed with ", task.exception)
        }
    }
}

Step 3. Call the method defined in step 2. Pass an object that you defined in step 1. This object will be invoked from the method in step 2, so the overridden method will receive the value/object as a parameter, which you can then assign to a variable of the same type. You can also pass other parameters of different types, but you have to update the method signature in step 2

fun saveModulesToFirestore(colName: String, saveCode: String) {

    var collectionExists = false

    if (saveCode == FirebaseHelper.ADD_TO_COLLECTION){
        nameExists(object: OnMetaReturnedListener{
            override fun onMetaReturned(colExists: Boolean) {
                collectionExists = colExists
                if (collectionExists){
                    val dialog = CollectionExistsDialogFragment.newInstance()
                    dialog.show(fragmentManager, DIALOG_COLLECTION_EXISTS)
                    return
                } else{
                    addNewCollectionToFirebase(colName)
                }
            }
        }, colName)
    }

}
Pitos
  • 659
  • 6
  • 23