【问题标题】:ListAdapter fetches the right list but does not update the valuesListAdapter 获取正确的列表但不更新值
【发布时间】:2020-08-13 16:44:59
【问题描述】:

我有以下功能-

    private fun fetchGroupData(callback: (groupModelList: List<GroupModel>) -> Unit) {
        val groupModelList = mutableListOf<GroupModel>()
        groupViewmodel.getAllGroupEntities().observeOnce(requireActivity(), Observer { groupEntityList ->
            groupEntityList.forEach { groupEntity ->
                /*
                We iterate though all of the available groups,
                for each group we get all of it's groupMembers models
                */
                val groupName = groupEntity.groupName
                val groupId = groupEntity.id
                taskViewmodel.getGroupTaskCounter(groupId).observeOnce(requireActivity(), Observer { groupTaskCount ->
                    /*
                    For each group we observe it's task counter
                     */
                    groupViewmodel.getGroupMembersForGroupId(groupId).observeOnce(requireActivity(), Observer { groupMembers ->
                        /*
                        For each group, we iterate through all of the groupMembers and for each of them we use it's userId
                         to fetch the user model, getting it's full name and adding it to a list of group users full name.
                         */
                        val groupUsersFullNames = mutableListOf<String>()
                        groupMembers.forEach { groupMember ->
                            val memberId = groupMember.userId
                            groupViewmodel.getGroupParticipantForUserId(memberId).observeOnce(requireActivity(), Observer { groupUser ->
                                groupUsersFullNames.add(groupUser.fullName)
                                /*
                                When the groupUsersFullNames size matches the groupMembers size, we can add a model to our list.
                                 */
                                if (groupUsersFullNames.size == groupMembers.size)
                                    groupModelList.add(GroupModel(groupId, groupName, groupTaskCount, groupUsersFullNames))
                                /*
                                When the new list matches the size of the group list in the DB we call the callback.
                                 */
                                if (groupModelList.size == groupEntityList.size)
                                    callback(groupModelList)
                            })
                        }
                    })
                })
            }
        })
    }

下面的函数正在使用它-

 private fun initAdapter() {
        fetchGroupData { groupModelList ->
            if (groupModelList.isEmpty()) {
                binding.groupsListNoGroupsMessageTitle.setAsVisible()
                binding.groupsListNoGroupsMessageDescription.setAsVisible()
                return@fetchGroupData
            }
            binding.groupsListNoGroupsMessageTitle.setAsGone()
            binding.groupsListNoGroupsMessageDescription.setAsGone()
            val newList = mutableListOf<GroupModel>()
            newList.addAll(groupModelList)
            adapter.submitList(groupModelList)
            Log.d("submitList", "submitList")
            binding.groupsListRecyclerview.setAdapterWithItemDecoration(requireContext(), adapter)
        }
    }

这 2 个函数代表从我的本地数据库中获取组列表到 RecyclerView。

为了在创建新组时收到通知,我持有一个共享 ViewModel 对象,该对象带有一个指示是否已创建新组的布尔值。

在编写这两个函数 ^ 的同一个片段中,我正在观察这个布尔值,如果值为 true,我会触发整个列表的重新获取 -

private fun observeSharedInformation() {
        sharedInformationViewModel.value.groupCreatedFlag.observe(requireActivity(), Observer { hasGroupBeenCreated ->
            if (!hasGroupBeenCreated) return@Observer
            sharedInformationViewModel.value.groupCreatedFlag.value = false
            Log.d("submitList", "groupCreatedFlag")
            initAdapter()

        })
    }

在我的代码中的某个时刻,在另一个 Fragment 中也有我的共享 ViewModel 的实例,我触发了我的 Boolean LiveData 的值更改 -

sharedInformationViewModel.value.groupCreatedFlag.value = true

这又会触发观察者,并重新获取我的组列表。

我面临的问题是,在重新获取新列表时(因为添加了一个新组),我确实获得了当前信息,并且一切都应该 100% 正常工作,但是新数据 - 新创建的组- 不出现。

新增的数据在2种情况下出现在列表中-

  1. 我重新启动应用程序
  2. 再次触发该功能 - 现在发生的情况是我看到了包含先前新添加组的列表,但没有出现要添加的最新组。

这个问题有一个例外 - 如果组列表为空,当我提交包含一个组的列表时,确实会出现要添加的第一个组。

我错过了什么?

编辑 -

这是我的适配器。

我正在使用一个名为 DefaultAdapterDiffUtilCallback 的自定义调用,它需要一个模型来实现为每个模型定义唯一 ID 的接口,以便我可以比较新旧模型。

class GroupsListAdapter(
    private val context: Context,
    private val onClick: (model : GroupModel) -> Unit
) : ListAdapter<GroupModel, GroupsListViewHolder>(DefaultAdapterDiffUtilCallback<GroupModel>()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupsListViewHolder {
        val binding = GroupsListViewHolderBinding.inflate(LayoutInflater.from(context), parent, false)
        return GroupsListViewHolder(binding)
    }

    override fun onBindViewHolder(holder: GroupsListViewHolder, position: Int) {
        holder.bind(getItem(position), onClick)
    }

    override fun submitList(list: List<GroupModel>?) {
        super.submitList(list?.let { ArrayList(it) })
    }
}


/**
 * Default DiffUtil callback for lists adapters.
 * The adapter utilizes the fact that all models in the app implement the "ModelWithId" interfaces, so
 * it uses it in order to compare the unique ID of each model for `areItemsTheSame` function.
 * As for areContentsTheSame we utilize the fact that Kotlin Data Class implements for us the equals between
 * all fields, so use the equals() method to compare one object to another.
 */
class DefaultAdapterDiffUtilCallback<T : ModelWithId> : DiffUtil.ItemCallback<T>() {
    override fun areItemsTheSame(oldItem: T, newItem: T) =
        oldItem.fetchId() == newItem.fetchId()

    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(oldItem: T, newItem: T) =
        oldItem == newItem
}

/**
 * An interface to determine for each model in the app what is the unique ID for it.
 * This is used for comparing the unique ID for each model for abstracting the DiffUtil Callback
 * and creating a default general one rather than a new class for each new adapter.
 */
interface ModelWithId {
        fun fetchId(): String
}


data class GroupModel(val id: String, val groupName: String, var tasksCounter: Int, val usersFullNames: List<String>) : ModelWithId {

    override fun fetchId(): String = id
}


编辑 2.0 -

我的observeOnce() 分机-

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
    observe(lifecycleOwner, object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}

【问题讨论】:

    标签: android android-recyclerview android-livedata android-listadapter


    【解决方案1】:

    你使用的是“新”ListAdapter吗?

    import androidx.recyclerview.widget.ListAdapter

    在这种情况下,我可以想出一个答案来解决您的问题。但由于我不知道更多关于你的具体实现,它是基于我的假设,你必须验证它是否适用。

    对于这个ListAdapter,您必须实现areItemsTheSameareContentsTheSame 方法。 我曾经遇到过类似的问题。我正在提交列表,但它只是没有更新视图中的列表。

    我可以通过仔细检查我如何比较内容是否相同来解决此问题。

    对于您的比较函数,请考虑以下几点:

        override fun areContentsTheSame(oldItem: GroupModel, newItem: GroupModel): Boolean {
            // assuming GroupModel is a class
            // this comparison is most likely not getting the result you want
            val groupModelsAreMatching = oldItem == newItem // don't do this
            
            // for data classes it usually gives the expected result
            val exampleDataClassesMatch = oldItem.dataClass == newItem.dataClass
            // But: the properties that need to be compared need to be declared in the primary constructor
            // and not in the function body
    
            // compare all relevant custom properties
            val groupIdMatches = oldItem.groupId == newItem.groupId
            val groupNameMatches = oldItem.groupName == newItem.groupName
            val groupTaskCountMatches = oldItem.groupTaskCount == newItem.groupTaskCount
            val groupUsersFullNamesMatches = oldItem.groupUsersFullNames == newItem.groupUsersFullNames
    
            return groupIdMatches && groupNameMatches && groupTaskCountMatches && groupUsersFullNamesMatches
    }
    

    当然你需要确保areItemsTheSame。这里只需要比较groupIds即可。

    你已经这样做了吗?

    【讨论】:

    • @AlonShlider 您的 observeOnce 方法究竟是如何工作的,尤其是在这个嵌套用例中?实现是否有点类似于这个带有实时数据的单个事件观察者示例? stackoverflow.com/a/56063739/13138026
    • @AlonShlider 如果我错了,请纠正我。所以你有一个名为fetchGroupData 的专用函数。然后在这个函数中,你有嵌套调用 LiveData Observers 最终将你的数据聚集在一起(异步)然后你再次删除观察者,这样它就不会在你再次触发 fetch 函数的情况下再次更新。这是你正在尝试的吗?
    • @AlonShlider 然后我想知道:在这里使用 LiveData 的目的是什么,而实际上您所做的只是调用一个函数来获取最新数据,而另一方面又不想使用实时数据“观察”变化的功能(例如您的 groupModel 数据的变化)?
    • 那我明白了,观察数据是未来发展的明确要求。您真正需要观察哪些属性的变化?我仍然看不到使用 3 层嵌套观察者的用例或必要性。上面您已经提到“添加新组”是您要观察的变化。
    • 您还提到,在不同的片段中,您有一个虚拟的 livedata 布尔值来触发模型的重新获取。当还观察数据库中的实际变化时,这个变量的目的是什么?在我看来,这个布尔实时数据也可以用于触发一次性事件。您可以为此使用单个实时数据事件(因此之后无需将值更改为 false ...)
    【解决方案2】:

    我发现了问题。

    这与我的获取逻辑无关。

    问题如下-

    创建组时,我将一个新的 Fragment 添加到 backstack 并在完成后将其弹出。

    删除组时,我在使用 popUpTopopUpToInclusive 时向前导航到我的主要片段 - 效果很好。

    我需要使用导航而不是向后弹出堆栈才能看到新列表。

    我花了 3 天的时间才弄明白。天哪

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      • 2021-05-16
      • 2016-12-23
      相关资源
      最近更新 更多