如何访问Firebase方法内部变量并计算收支总余额?
解决Firebase异步回调中变量无法外部获取的问题
我太懂你这个困扰了!Firebase的数据监听是异步执行的,你在外部声明变量想直接拿回调里的值根本行不通——因为你的余额计算代码会比回调里的代码先跑,那时候回调还没拿到数据呢,变量自然是空的或者不对的。下面给你两种实用的解决办法,按需选就行:
方案一:用类成员变量存储,实时计算余额
这个方案适合需要实时监听数据变化的场景(比如数据库里的收入/支出更新后,余额自动跟着变)。
第一步:在类里声明成员变量
先在你的Activity/Fragment里定义两个全局变量,用来存计算好的收入和支出总和:
private var incomeTotal: Double = 0.0 private var spentTotal: Double = 0.0
第二步:修改收入监听的回调
把计算好的收入总和更新到成员变量里,然后调用计算余额的方法:
val incomeSumReference = database.getReference("users").child(uid).child("Incomes").child(currentYear.toString()).child(currentMonthInString.toString()) incomeSumReference.addValueEventListener(object : ValueEventListener { @SuppressLint("SetTextI18n") @RequiresApi(Build.VERSION_CODES.N) override fun onDataChange(snapshot: DataSnapshot) { var sum = 0.0 for (dataSnapshot in snapshot.children) { // 这里加个空安全处理,避免崩溃 val value = (dataSnapshot.getValue() as? Map<*, *>)?.get("itemValue") sum += value?.toString()?.toDouble() ?: 0.0 } val incomeSumToFormated = formatNumberString(sum.toString()) profitTextView.text = "+$incomeSumToFormated" // 更新成员变量 incomeTotal = sum // 检查是否可以计算余额 calculateTotalBalance() } override fun onCancelled(error: DatabaseError) { // 别忘了加错误处理,比如给用户弹个Toast提示 } })
第三步:修改支出监听的回调
和收入的逻辑一样,更新支出的成员变量后调用计算方法:
val spentSumReference = database.getReference("users").child(uid).child("Expenses").child(currentYear.toString()).child(currentMonthInString.toString()) spentSumReference.addValueEventListener(object : ValueEventListener { @RequiresApi(Build.VERSION_CODES.N) override fun onDataChange(snapshot: DataSnapshot) { var sum = 0.0 for (dataSnapshot in snapshot.children) { val value = (dataSnapshot.getValue() as? Map<String, Any>)?.get("itemValue") sum += value?.toString()?.toDouble() ?: 0.0 } val spentSumToFormated = formatNumberString(sum.toString()) spentTextView.text = "-$spentSumToFormated" // 更新成员变量 spentTotal = sum // 检查是否可以计算余额 calculateTotalBalance() } override fun onCancelled(error: DatabaseError) { // 错误处理 } })
第四步:添加计算余额的方法
每次收入或支出的数据更新后,都会调用这个方法,只有当两个值都就绪时,才会计算并更新余额:
@RequiresApi(Build.VERSION_CODES.N) private fun calculateTotalBalance() { val total = incomeTotal - spentTotal val formattedTotal = formatNumberString(total.toString()) // 根据正负显示不同的符号 totalBalance.text = if (total >= 0) "+$$formattedTotal" else "-$$formattedTotal" }
方案二:用Kotlin协程转异步为同步(一次性加载)
如果你的需求是一次性加载数据,不需要实时监听变化,用协程的callbackFlow把Firebase的回调转成流,代码会更简洁:
第一步:定义获取收入/支出的 suspend 函数
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import androidx.lifecycle.lifecycleScope // 获取当月收入总和 private suspend fun getIncomeSum(): Double { return callbackFlow { val ref = database.getReference("users").child(uid).child("Incomes").child(currentYear.toString()).child(currentMonthInString.toString()) val listener = object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { var sum = 0.0 snapshot.children.forEach { val value = (it.getValue() as? Map<*, *>)?.get("itemValue") sum += value?.toString()?.toDouble() ?: 0.0 } trySend(sum).isSuccess close() } override fun onCancelled(error: DatabaseError) { close(error.toException()) } } ref.addListenerForSingleValueEvent(listener) awaitClose { ref.removeEventListener(listener) } }.first() } // 获取当月支出总和 private suspend fun getSpentSum(): Double { return callbackFlow { val ref = database.getReference("users").child(uid).child("Expenses").child(currentYear.toString()).child(currentMonthInString.toString()) val listener = object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { var sum = 0.0 snapshot.children.forEach { val value = (it.getValue() as? Map<String, Any>)?.get("itemValue") sum += value?.toString()?.toDouble() ?: 0.0 } trySend(sum).isSuccess close() } override fun onCancelled(error: DatabaseError) { close(error.toException()) } } ref.addListenerForSingleValueEvent(listener) awaitClose { ref.removeEventListener(listener) } }.first() }
第二步:在协程里计算并更新UI
private fun loadTotalBalance() { lifecycleScope.launch { try { val income = getIncomeSum() val spent = getSpentSum() val total = income - spent // 更新各个TextView profitTextView.text = "+$${formatNumberString(income.toString())}" spentTextView.text = "-$${formatNumberString(spent.toString())}" val formattedTotal = formatNumberString(total.toString()) totalBalance.text = if (total >= 0) "+$$formattedTotal" else "-$$formattedTotal" } catch (e: Exception) { // 处理加载失败的情况 } } }
最后提醒一句:一定要做好空安全处理,比如map["itemValue"]可能为空,直接强转或者转Double容易崩溃,用?: 0.0兜底就稳多了。
内容的提问来源于stack exchange,提问作者Eren Ahmed
相关产品推荐
相关产品推荐

