【问题标题】:How to refresh the UI of a fragment using LiveData after Firestore data loadsFirestore 数据加载后如何使用 LiveData 刷新片段的 UI
【发布时间】:2026-02-03 18:10:02
【问题描述】:

我正在使用 Kotlin 和 Firebase 产品制作 Android 应用。我与 Firestore 成功连接,并且可以成功检索我想要的数据,但我很难在 RecyclerView 中显示它。

当应用程序加载并且用户登录后,我的 Firestore 查询使用用户的 UID 来获取他们的分配列表。使用日志我可以看到当主屏幕加载时,这没有问题。在主屏幕片段中,我有 RecyclerView 的数据绑定并设置我的 ViewModel 以让片段观察返回的 Firestore 数据。

我认为我对 LiveData 的工作原理存在误解,因为如果我点击主屏幕的底部导航图标以触发 UI 刷新,然后列表会填充,我可以根据需要使用该应用程序。因此,我的观察者/LiveData 不能正确设置,因为一旦数据发生变化(空列表到非空列表),它就不会自动刷新。

由于我是编程新手,我确信自己陷入了许多陷阱并且做错了一些事情,但几个月来我一直在通过 * 和 YouTube 寻求有关此问题的帮助。不幸的是,我没有将所有链接保存到每个视频和每个帖子中。

我尝试将 ViewModel 和 Repository/Database 类(单例)调整为不同的效果,目前我处于最佳版本,只需单击一下即可刷新 UI。以前它需要多次点击。

来自数据库类

private val assignments = MutableLiveData<List<AssignmentModel>>()

private fun getUserAssignments(c: ClassModel) {
        val assignmentQuery = assignmentRef.whereEqualTo("Class_ID", c.Class_ID)

        assignmentQuery.addSnapshotListener { documents, _ ->
            documents?.forEach { document ->
                val a = document.toObject(AssignmentModel::class.java)
                a.Assignment_ID = document.id
                a.Class_Title = c.Title

                a.Formatted_Date_Due = formatAssignmentDueDate(a)

                assignmentMap[a.Assignment_ID] = a
            }
        }
    }

    fun getAssignments() : LiveData<List<AssignmentModel>> {
        assignments.value = assignmentMap.values.toList().filter {
            if (it.Date_Due != null) it.Date_Due!!.toDate() >= Calendar.getInstance().time else true }
            .sortedBy { it.Date_Due }
        return assignments
    }

来自 ViewModel

class AssignmentListViewModel internal constructor(private val myDatabase: Database) : ViewModel() {

    private var _assignments: LiveData<List<AssignmentModel>>? = null

    fun getAssignments() : LiveData<List<AssignmentModel>> {
        var liveData = _assignments
        if (liveData == null) {
            liveData = myDatabase.getAssignments()
            _assignments = liveData
        }
        return liveData
    }
}

来自片段

class AssignmentList : Fragment() {
    private lateinit var model: AssignmentListViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = AssignmentListBinding.inflate(inflater, container, false)

        val factory = InjectorUtils.provideAssignmentListViewModelFactory()
        model = ViewModelProvider(this, factory).get(AssignmentListViewModel::class.java)

        val assignmentAdapter = AssignmentAdapter()
        binding.assignmentRecycler.adapter = assignmentAdapter
        updateUI(assignmentAdapter)
        return binding.root
    }

    private fun updateUI(adapter: AssignmentAdapter) {
        model.getAssignments().observe(this, Observer { assignments ->
            if (assignments.isNotEmpty()) adapter.submitList(assignments)
        })
    }
}

再次,我希望 RecyclerView 在 Firestore 中的数据出现后自动填充,但事实并非如此。在我点击主屏幕按钮之前,屏幕一直是空的。

这些 sn-ps 显示了我最近所做的更改。最初我有 Firestore 查询函数直接返回 LiveData。我还有一个更简单的 ViewModel,比如 fun getAssignments() = myDatabase.getAssignments()。

感谢所有帮助和建议。

【问题讨论】:

    标签: android firebase android-fragments kotlin android-livedata


    【解决方案1】:

    在解决此问题时,我建议从两件事着手。

    查看更新 LiveData 的位置/时间

    目标是每当 Firebase 中的数据更新时,您的 assignments LiveData 就会更新您的 UI。比如:

    1. Firestore 更新
    2. Firestore 触发 SnapshotListener
    3. SnapshotListener 更新 LiveData
    4. LiveData 观察者更新 UI

    因此,在您的快照侦听器中,您应该更新您的 LiveData,这是我认为您缺少的。所以它会是这样的:

    // Where you define your SnapshotListner
    assignmentQuery.addSnapshotListener { documents, _ ->
        // Process the data
        documents?.forEach { document ->
            val a = document.toObject(AssignmentModel::class.java)
            a.Assignment_ID = document.id
            a.Class_Title = c.Title
    
            a.Formatted_Date_Due = formatAssignmentDueDate(a)
    
            assignmentMap[a.Assignment_ID] = a
        }
    
        // Update your LiveData
        assignments.value = assignmentMap.values.toList().filter {
        if (it.Date_Due != null) it.Date_Due!!.toDate() >= Calendar.getInstance().time else true }
        .sortedBy { it.Date_Due }
    
    }
    

    现在,每次您的 Firestore 更新时,您的 LiveData 都会更新,您的 UI 也应该更新。

    鉴于代码更改,getAssignments() 可以只返回分配。你可以使用 Kotlin backing property,覆盖 here

    private val _assignments = MutableLiveData<List<AssignmentModel>>()
    val assignments: LiveData<List<AssignmentModel>>
        get() = _assignments
    

    至于为什么它现在不工作,现在你在启动时拨打getAssignments()。这将过滤一个空的assignmentMap.values(我相信 - 可能值得检查),因为当它被调用时,Firebase 还没有完成为您获取任何数据。当 Firebase 确实获得新数据时,它会触发侦听器,但您不会更新 LiveData。

    注意你在哪里设置你的听众/观察者

    LiveData 观察者和 Firebase 监听器的一个棘手问题是确保您只设置它们一次。

    对于您的 Firebase 侦听器,您应该在初始化数据库时设置侦听器,而不是每次调用 getUserAssignments 时设置侦听器。然后您就不需要在 ViewModel 中进行所有空值检查,这基本上可以确保 ViewModel 至少不会调用 getUserAssignments 两次......但是如果您有其他类与您的数据库交互,它们可能会调用 getUserAssignments 多个次,然后你就会有大量额外的听众。

    另外,请确保您detach your listener

    Doug Stevenson 的演讲 Firebase and Android Jetpack: Fit Like a Glove 中描述了一种处理此问题的方法 - 该演讲包括一个演示代码 here。与此相关的部分是他如何处理LiveData——注意该类如何包括添加和删除侦听器。 TL;DR 是他使用LiveData's lifecycle awareness 自动进行 Firebase 侦听器设置和清理。完成的过程有点复杂,所以我建议观看谈话from here

    对于您的 LiveData,setup/tear down 看起来是正确的,因为它是在 onCreateView 中设置的(并且由于它具有生命周期感知能力而自动拆除)。我可能会将updateUI 重命名为setupUIObservation,因为updateUI 听起来像是您多次调用的名称。与 Firebase 侦听器一样,您要确保不会多次设置同一个 LiveData 观察者。

    希望有帮助!

    【讨论】:

    • 谢谢你,莱拉!你是对的,这完全取决于我在哪里尝试更新我的 LiveData。我曾尝试在函数中分配 LiveData 以从 Firestore 获取数据,但我没有在快照侦听器本身中尝试过。现在,UI 会随着数据加载而自动刷新,甚至会使用 Firestore 对特定文档的更新进行刷新。 :) 我知道我的 Firebase 监听器可能也有一些问题。我来看看Doug的演讲。感谢您的帮助!
    • 我认为将片段“this”的生命周期设置为观察方法的第一个参数是不正确的,因为每次按下应用程序的任务或主页按钮时都会重新创建片段的视图,这意味着您的观察者不会撕裂向下作为片段的视图,这是因为您的观察者与整个片段生命周期而不是视图生命周期相关联。这意味着在下次您重新打开应用程序时 onCreateView 方法将创建新的观察者来监听片段生命周期但不会撕裂除非片段本身被销毁,否则这种方法最终会导致您拥有多个观察者
    • 建议你使用getViewLifecycleOwner()而不是使用片段生命周期“this”
    • 如果你想用户fragment生命周期你最好从fragment的onCreate()调用
    • @Lyla 可以看看这个*.com/questions/63007155/…