观察MediatorLiveData问题 [英] Observing MediatorLiveData Issue
问题描述
我的ViewModel
中有以下LiveData
变量(简化示例):
I have the following LiveData
variables in my ViewModel
(simplified example):
val currentUser : LiveData<UserObject>
val allSites : LiveData<ArrayList<SiteObject>>
val filterSitesForUser : LiveData<Boolean>
val orderSitesByField : LiveData<String>
val orderSitesDirection : LiveData<Query.Direction>
val searchFilterSitesText : LiveData<String>
我正在尝试使用MediatorLiveData
将一个数据流"连接到我的RecyclerView
.
I'm trying to use MediatorLiveData
to have one 'stream' of data connecting to my RecyclerView
.
因此,我在ViewModel
中也有以下代码,在Fragment
中可以看到:
I therefore also have the following code in the ViewModel
, which is observed in the Fragment
:
fun sitesList() : LiveData<ArrayList<SiteObject>> {
val result = MediatorLiveData<ArrayList<SiteObject>>()
result.addSource(currentUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(allSites) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(filterSitesForUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesByField) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesDirection) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(searchFilterSitesText) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
return result
}
..与此同时,也在ViewModel
中:
..along with this, also in the ViewModel
:
private fun combineSitesData (
currentUser: LiveData<UserObject>,
allSites: LiveData<ArrayList<SiteObject>>,
filterSitesForUser: LiveData<Boolean>,
orderSitesByField: LiveData<String>,
orderSitesDirection: LiveData<Query.Direction>,
searchFilterSitesText: LiveData<String>
) : ArrayList<SiteObject> {
var sitesList = ArrayList<SiteObject>()
val userId = currentUser.value?.userID
if (userId == null || allSites.value == null) {
Log.d(TAG, "combineSitesData() - currentUser or allSites value null")
return sitesList
}
when (filterSitesForUser.value) {
true -> sitesList.addAll(allSites.value!!.filter { site -> site.users.contains(userId) })
false -> sitesList.addAll(allSites.value!!)
}
if (orderSitesDirection.value == Query.Direction.ASCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortBy { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortBy { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortBy { it.siteTask }
SITE_RATING -> sitesList.sortBy { it.siteRating }
else -> sitesList.sortBy {it.siteReference}
}
}
if (orderSitesDirection.value == Query.Direction.DESCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortByDescending { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortByDescending { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortByDescending { it.siteTask }
SITE_RATING -> sitesList.sortByDescending { it.siteRating }
else -> sitesList.sortByDescending {it.siteReference}
}
}
if (!searchFilterSitesText.value.isNullOrEmpty()) {
var filteredList = ArrayList<SiteObject>()
var filterPattern = searchFilterSitesText.value.toString().toLowerCase().trim()
for (site in sitesList) {
if(site.siteReference.toLowerCase().contains(filterPattern) || site.siteAddress.toLowerCase().contains(filterPattern)) {
filteredList.add(site)
}
}
Log.d(TAG, "combineSitesData() - returned filteredList (size = ${filteredList.size})")
return filteredList
}
Log.d(TAG, "combineSitesData() - returned sitesList (size = ${sitesList.size})")
return sitesList
}
这是Fragment
中观察站点列表的代码:
This is the code in the Fragment
which observes the list of Sites:
// Observe ViewModel SitesList
businessViewModel.sitesList().observe(viewLifecycleOwner, Observer { sitesList ->
if (sitesList != null) {
businessViewModel.currentUser.value?.userID?.let { sitesAdapter.setList(it, sitesList) }
sitesAdapter.notifyDataSetChanged()
Log.d (TAG, "setupViewModelObservers(): businessViewModel.sitesList().size = ${sitesList.size}" )
}
})
它似乎与第一个观察到的事实相反,它触发了六次,因此更新了我的RecylcerView六次-当这是一个很大的列表时,我认为这有点问题!
It seems to be working apart from the fact that when it is first observes, it fires off six times, which therefore updates my RecylcerView six times - when this is a large list, I see this being a bit of an issue!
初始化之后,它仅在LiveData发生任何更改时才更新一次,所以没关系.
After it initialises, it only updates once when any of the LiveData's change, so that's ok.
我不确定如何避免这种情况的发生,而没有在Fragment或ViewModel中保存一个列表以将观察到的列表与之进行比较(我试图避免这种情况(因为尝试遵循MVVM架构).).
I'm not sure how to prevent this from happening, without holding a list in the Fragment or ViewModel to compare observed list to, which I am trying to avoid (as trying to follow MVVM architecture)..
为清楚起见,我添加了RecyclerViewAdapter
来显示功能setList
的作用:
for clarity, I have added my RecyclerViewAdapter
to show what the function setList
does:
class SitesRecyclerViewAdapter(
private var currentUserId: String,
private val onItemClickedListener: (SiteObject) -> Unit,
private val onItemLongClickedListener: (SiteObject) -> Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var sitesList = ArrayList<SiteObject>()
class SiteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItemBinding = SitesListItemBinding.bind(itemView)
fun bind(
userId: String,
site: SiteObject,
clickListener: (SiteObject) -> Unit,
longClickListener: (SiteObject) -> Boolean
) {
// Set text fields
listItemBinding.sitesItemTitleText.text = site.siteReference
// listItemBinding.sitesItemProjectsText.text = site.recentProjectsText TODO: Create way of showing recent projects with arrays
//Reset Icons visibility
listItemBinding.sitesItemTaskImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating1ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating2ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating3ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemFavouriteImageView.visibility = View.GONE
//Set sitePriority Icon visibility
if (site.siteTask) listItemBinding.sitesItemTaskImageView.visibility = View.VISIBLE
//Set siteRating Icon visibility
when(site.siteRating){
1 -> listItemBinding.sitesItemRating1ImageView.visibility = View.VISIBLE
2 -> listItemBinding.sitesItemRating2ImageView.visibility = View.VISIBLE
3 -> listItemBinding.sitesItemRating3ImageView.visibility = View.VISIBLE
}
//Set siteFavourite Icon visibility
if (site.users.contains(userId)) listItemBinding.sitesItemFavouriteImageView.visibility = View.VISIBLE
// Set Listeners
listItemBinding.sitesItemCardview.setOnClickListener { clickListener(site) }
listItemBinding.sitesItemCardview.setOnLongClickListener { longClickListener(site) }
// Not set map listener?
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// LayoutInflater: takes ID from layout defined in XML.
// Instantiates the layout XML into corresponding View objects.
// Use context from main app -> also supplies theme layout values!
val inflater = LayoutInflater.from(parent.context)
// Inflate XML. Last parameter: don't immediately attach new view to the parent view group
val view = inflater.inflate(R.layout.sites_list_item, parent, false)
return SiteViewHolder(view)
}
override fun getItemCount(): Int = sitesList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// Populate ViewHolder with data that corresponds to the position in the list
// which we are told to load
(holder as SiteViewHolder).bind(
currentUserId,
sitesList[position],
onItemClickedListener,
onItemLongClickedListener
)
}
fun setList(userID: String, observedSites: ArrayList<SiteObject>) {
currentUserId = userID
sitesList = observedSites
notifyDataSetChanged()
Log.d(TAG, "setList(), observedSites size = ${observedSites.size}")
}
}
推荐答案
您可以使用postValue
代替setValue
如果在主线程执行发布的任务之前多次调用此方法,则仅 最后一个值将被调度.
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
这篇关于观察MediatorLiveData问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!