我正在制作一个使用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()。
感谢所有的帮助和建议。
在排除此问题时,我建议从两个方面开始。
目标是只要Firebase中的数据更新,您的assignments
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观察器。
希望有帮助!