当前位置: 首页 > 知识库问答 >
问题:

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

尤俊誉
2023-03-14

我正在制作一个使用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个答案

裴承安
2023-03-14

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

目标是只要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观察器。

希望有帮助!

 类似资料:
  • 我试图解决一个问题,但没有成功。每当数据库(数据库室)中特定模型的记录发生变化时,我想更新我的回收器视图。我使用ViewModel来处理模型数据,记录列表存储在LiveData中。 数据库 模型 刀 视图模型 碎片 现在,我的问题是为什么观察者只被调用一次(在片段的开始),然后没有被再次调用?我如何让观察者不断地倾听数据库中的变化(插入,更新,删除),以便我的回收器视图可以立即更新?非常感谢任何建

  • 我从这个问题中编辑了一个柱塞

  • 我在我的应用程序中使用导航栏,每次我选择一个项目时,它都会加载一个片段。 我能够使用相同的文本字段和按钮保存片段的状态,但有一个片段可以加载地图、添加标记、集群并执行。 每次我转到另一个菜单项并返回时,它都会重新加载所有内容,例如AsyncTask、Cluster、Markers。我如何停止此片段以不再重新创建地图并在返回时保存状态: Udate 1:我更新了代码,但问题仍然存在 主要活动: 主片

  • 我正在尝试在我正在构建的天气应用程序中实现SwipeToRe新鲜布局。当用户滑动刷新时,ViewModel中的数据应该更新,然后视图应该相应地更新。 这是我的货币天气片段的片段: 我的视图模型: 我的lazyDeferred实现: 目前,通过此设置,forecastRepository。当在应用程序启动或切换到该片段时加载该片段时,会调用ViewModel中的getWeather()函数,但在滑动

  • 我遇到了一个如何在对话框片段中更新片段的问题。 当我单击过滤器菜单按钮时,会显示一个新的对话框片段,其中包括一个无线电组。 我想在单击ok按钮时更新包含位置列表的片段。 它是PlaceActive的代码,其中包含PlaceFraank: 公共类PlaceActive扩展AppCompatActive{ } 以下是PlaceFragment类的代码: 公共类PlaceFragment扩展了片段{ }

  • 我使用setUserVisibleHint()方法加载数据,但是这个方法在oncreateview之前调用,所以我得到的是NPE。 我已经检查了从下面提到的链接堆栈溢出的索洛。 请帮我做这个。我总共有5个碎片。在每个片段中,我都在fragment的onCreateView方法中进行服务器调用。