提问者:小点点

如何在Firestore数据加载后使用LiveData刷新片段的UI


我正在制作一个使用Kotlin和Firebase产品的Android应用程序。我与Firestore建立了成功的连接,并且可以成功地检索到我想要的数据,但是我很难在RecolyerView中显示它。

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

我相信这是我对LiveData如何工作的误解,因为如果我点击主屏幕底部的导航图标触发UI刷新,那么列表就会填充,我就可以根据需要使用该应用程序了。因此,我的Observer/LiveData不能正确设置,因为一旦数据发生更改(null list改为not null list)时,它不会自动刷新。

由于我是编程新手,我确信我已经陷入了许多陷阱,并且做了一些错误的事情,但是我已经在StackOverflow和YouTube上搜索了几个月来关于这个问题的帮助。不幸的是,我没有所有的链接保存到每一个视频和每一个帖子。

我已经尝试将ViewModel和Repository/Database类(singleton)调整到不同的效果,目前我的版本是最好的,只需要点击一下就可以刷新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)
        })
    }
}

同样,我希望一旦来自Firestore的数据出现,RecyclerView会自动填充,但它没有。在我点击“主屏幕”按钮之前,屏幕一直是空的。

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

感谢所有的帮助和建议。


共1个答案

匿名用户

在排除此问题时,我建议从两个方面开始。

目标是只要Firebase中的数据更新,您的assignmentsLiveData就会更新您的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支持属性来完成此操作,这里介绍了:

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

至于它目前无法工作的原因,现在您可以在启动时调用getAssignments()。这将过滤空的assignmentMap.values(我相信--可能值得检查),因为当调用它时,Firebase还没有为您获取任何数据。当Firebase获得新数据时,它会触发监听器,但您不会更新LiveData。

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

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

此外,请确保分离您的侦听器。

在Doug Stevenson的talk Firebase and Android Jetpack:Fit Like a Glove中描述了一种处理方法-talk在这里包含了一个演示代码。与此相关的部分是他如何处理LiveData--注意这个类是如何添加和删除侦听器的。TL;DR是他使用LiveData的生命周期感知来自动完成Firebase监听器的设置和清理。怎么做有点复杂,所以我建议从这里看演讲。

对于您的LiveData,setup/Tear down看起来是正确的,因为它是在onCreateView中进行设置的(并且通过它的生命周期感知的事实自动拆除)。我可以将updateui重命名为类似于setupuiobservation的东西,因为updateui听起来像是您多次调用的东西。与Firebase侦听器一样,您希望确保没有多次设置同一个LiveData观察器。

希望有帮助!