问题
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>
I'm trying to use MediatorLiveData
to have one 'stream' of data connecting to my RecyclerView
.
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
}
..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
}
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}" )
}
})
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!
After it initialises, it only updates once when any of the LiveData's change, so that's ok.
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)..
EDIT: 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}")
}
}
回答1:
You can use postValue
instead of setValue
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
来源:https://stackoverflow.com/questions/65862724/observing-mediatorlivedata-issue