【问题标题】:Condition 'listenerReg != null' is always 'true' when trying to detach/close Firestore snapshot (Android)尝试分离/关闭 Firestore 快照时,条件“listenerReg!= null”始终为“真”(Android)
【发布时间】:2020-06-01 20:14:28
【问题描述】:

上下文:这是我在 Android 中的第一个项目,我在问一些愚蠢的问题,但我找不到任何方向(当然,我把以前关于 Angular/Srping 的知识与 Android/Kotlin 搞混了)

目标:Android 应用会从某个后端微服务获取一个 Firestore 自定义令牌,然后开始监听一个文档。到现在为止还挺好。我阅读了关于如何关闭/分离监听的良好实践,我相信我已经通过将 Android 活动作为第一个参数传递给快照成功地做到了这一点。到目前为止也很好。但就我而言,我必须在 10 分钟后或收到文档中的特定值后关闭/分离快照监听。现在我真的卡住了。

我尝试了想象中最简单的第一步,并且从这个主题中得到了天真的警告。所以我的直截了当的问题是:为什么它总是抱怨为 ALWAYS TRUE 条件?来自 Android 专家的补充评论是如何在 10 分钟后关闭/分离快照,以及我是否从收听的文档中收到特定值。请接受这两个条件中的任何一个都需要停止侦听并仍然保留在相同 MainActivity.kt 中的想法。

这是在 onStop 循环阶段尝试检查时带有警告的代码

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()
    }
}
}

我知道,由于我将活动添加为快照的第一个参数,因此活动很快就会离开,它会自动分离监听。但是我还有两个条件可以停止听:

1 - 10 分钟后

2 - 如果我得到一个特定的返回值

所以作为想象中的解决方案,我会或多或少地尝试

...

        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())

【问题讨论】:

    标签: android firebase kotlin google-cloud-firestore


    【解决方案1】:

    回答您的问题:

    为什么它总是抱怨为 ALWAYS TRUE 条件?

    您的FirebaseFirestore 对象被初始化为lateinit var listenerReg : FirebaseFirestore,这意味着您已将您的listenerReg 变量标记为非空并且稍后将被初始化。 lateinit 用于将变量标记为尚未初始化,但基本上承诺将在代码中首次引用时对其进行初始化。如果尝试访问这个变量并且它没有被初始化,Kotlin 会在运行时抛出一个错误。

    如果你想让它可以为空,你需要像这样初始化它:

    var listenerReg : FirebaseFirestore? = null
    

    然后你可以让你的 stop 方法看起来像这样:

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

    但我还有两个条件可以停止收听: 1 - 10 分钟后

    有很多方法可以在 Android 上设置某些超时。最直接的方法是发布一个调用stopSnapshot() 方法的处理程序,例如:

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

    2 - 如果我得到一个特定的返回值

    收到此值后只需致电stopSnapshot()

    注意:我假设所有 Firestore 调用都是正确的。我没有 API 和这种用法。希望我的回答有帮助。

    【讨论】:

    • 谢谢。我是 Android/Kotlin 的新手,你肯定完全回答了我的问题。如果您不介意,最后的评论:我必须添加!在我调用某种 listenerReg 方法的地方。感谢Android Studio,我很容易修复它,但我不明白这一点。我刚刚看到消息“添加非空断言 (!!) 调用”并且我接受了修复建议。在我看来,这与 Java 中的 Optional.of 相同
    • !! 用于告诉 Kotlin 变量在使用时不为空。这样做是因为理论上如果您调用if(x != null) { },即使进入块后 if 语句通过,x 仍然可以为空(如果其他线程同时将其设置为空)。为了避免必须使用!!,您可以在变量上使用letapply 之类的东西,并在同步块中处理访问。阅读本文了解更多信息:kotlinlang.org/docs/reference/scope-functions.html
    猜你喜欢
    • 2020-06-16
    • 2022-01-11
    • 2018-08-25
    • 2019-09-19
    • 2013-11-18
    • 1970-01-01
    • 2014-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多