Jim C
Jim C

Reputation: 4385

Condition 'listenerReg != null' is always 'true' when trying to detach/close Firestore snapshot (Android)

Context: It is my first project in Android and I fill I am asking some silly question but I can't find any direction on it (certainly I am messing up some previous knowledge of Angular/Srping with Android/Kotlin)

Goal: Android App will get a Firestore customtoken from certain backend microservice and then start to listen a document. So far so good. I read about good practices of how close/detach the listen and I believe I have successfully done this by passing Android activity as first argument to snapshot. So far also so good. But in my case, I have to close/detach the snapshot listen either after 10 minutes or after a especific value from document is received. Now I really got stuck.

I tried the simplest first step as imagined and I am getting the naive warnning form this topic. So my straght question is: why it is complaining as ALWAYS TRUE condition? A complementaty comment from someone expert on Android would be how to close/detach the snapshot after 10 minutes and if I receive a specific value from the listened document. Please accept the idea that either one of these two conditions needs to stops listen and still keep in same MainActivity.kt.

Here is the code with warning when trying to check during onStop cycle phase

package com.mycomp.appfirestore

import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.EventListener
import com.google.firebase.firestore.FirebaseFirestore
import com.mycomp.appfirestore.data.service.Endpoint
import com.mycomp.appfirestore.data.service.NetworkUtils
import com.mycomp.appfirestore.model.Transfer
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


class MainActivity : AppCompatActivity() {

    lateinit var auth: FirebaseAuth

    lateinit var listenerReg : FirebaseFirestore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnTransfer = findViewById(R.id.btnTransfer) as Button
        val textViewTransfer = findViewById(R.id.textViewTransfer) as TextView
        btnTransfer.setOnClickListener {
            getToken()
        }
    }

    fun getToken() {
        val retrofitClient = NetworkUtils
            .getRetrofitInstance("http://192.168.15.13:8080/")

        val endpoint = retrofitClient.create(Endpoint::class.java)

...
        callback.enqueue(object : Callback<Transfer> {
            override fun onFailure(call: Call<Transfer>, t: Throwable) {
                Toast.makeText(baseContext, t.message, Toast.LENGTH_SHORT).show()
            }

            override fun onResponse(call: Call<Transfer>, response: Response<Transfer>) {
                listenStatus()
            }
        })

    }

    fun listenStatus() {
        val TAG = "ListenStatus"
        auth = FirebaseAuth.getInstance()

        // to make simple for this question let's say the backend returned a valid customtoken used here
        auth.signInWithCustomToken("eyJ **** gXsQ")
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    Log.d(TAG, "*** signInWithCustomToken:success")
                    startSnapshot()
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCustomToken:failure", task.exception)
                    Toast.makeText(
                        baseContext, "Authentication failed.",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
    }


    fun startSnapshot() {
        val TAG = "StartSnapshot"

        //Try to pass this(activity context) as first parameter.It will automatically handle acivity life cycle.
        // Example if you are calling this listener in onCreate() and passing this as a first parameter then
        // it will remove this listener in onDestroy() method of activity.
        listenerReg = FirebaseFirestore.getInstance()

        listenerReg.collection("transfer")
            .document("sDme6IRIi4ezfeyfrU7y")
            .addSnapshotListener(
                this,
                EventListener<DocumentSnapshot?> { snapshot, e ->
                    if (e != null) {
                        Log.w(TAG, "Listen failed.", e)
                        return@EventListener
                    }
                    if (snapshot != null && snapshot.exists()) {
                        textViewTransfer.text = snapshot.data!!["status"].toString()
                        //Log.d(TAG, snapshot.data!!["status"].toString())
                    } else {
                        Log.d(TAG, "Current data: null")
                    }
                })

    }
//here I get the warnning mentioned in my question topic
fun stopSnapshot() {
    if (listenerReg != null) {
        listenerReg.remove()
    }
}
}

I am aware that since I added the activity as first argument of snapshot soon the activity is left it will detached automatically the listen. But I have two more condition to stop listen:

1 - after 10 minutes

2 - if I get a specific returned value

So as imaginary solution I would try more or less

...

        EventListener<DocumentSnapshot?> { snapshot, e ->
            if (e != null) {
                Log.w(TAG, "Listen failed.", e)
                return@EventListener
            }
            if (snapshot != null && snapshot.exists()) {
                textViewTransfer.text = snapshot.data!!["status"].toString()
                **** imagined solution ****
                if (snapshot.data!!["status"].toString() == "FINISH"){
                   stopSnapshot()
                }
                //Log.d(TAG, snapshot.data!!["status"].toString())
            } else {
                Log.d(TAG, "Current data: null")
            }
        })
        ...
        **** imagined solution ***
        listenerReg.timeout(10000, stopSnapshot())

Upvotes: 3

Views: 2921

Answers (1)

chef417
chef417

Reputation: 544

To answer your questions:

Why it is complaining as ALWAYS TRUE condition?

Your FirebaseFirestore object is initialized as lateinit var listenerReg : FirebaseFirestore, which means you've marked your listenerReg variable as non-null and to be initialized later. lateinit is used to mark the variable as not yet initialized, but basically promising that it will be initialized when first referenced in code. Kotlin will throw an error at runtime if trying to access this variable and it's not initialized.

If you want to make it nullable, you'd need to initialize it like this:

var listenerReg : FirebaseFirestore? = null

Then you could have your stop method looking something like this:

fun stopSnapshot() {
    if (listenerReg != null) {
        listenerReg.remove()
        listenerReg.terminate()
        listenerRef = null
    }
}

But I have two more condition to stop listen: 1 - after 10 minutes

There are many ways to set certain timeouts on Android. The most straightforward way would be to post a handler that will invoke stopSnapshot() method, e.g.:

Handler().postDelayed({
    //Do something after 10mins
    stopSnapshot();
}, 1000 * 60 * 10)

2 - if I get a specific returned value

Just call stopSnapshot() when you receive this value.

Note: I assumed all the Firestore calls are correct. I don't have the API and this usage on top of my head. Hope my answer helps.

Upvotes: 3

Related Questions