Observing MediatorLiveData Issue

穿精又带淫゛_ 提交于 2021-02-11 12:32:23

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!