I have the following fuction -
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)
})
}
})
})
}
})
}
That is being used by the following function -
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)
}
}
These 2 functions represent group list fetch from my local DB into a RecyclerView.
In order to be notified when a new group has been created, I am holding a shared ViewModel object with a boolean indicating if a new group has been created.
In the same Fragment that these 2 functions ^ are written, I am observing this Boolean, and if the value is true I trigger a re-fetch for the entire list -
private fun observeSharedInformation() {
sharedInformationViewModel.value.groupCreatedFlag.observe(requireActivity(), Observer { hasGroupBeenCreated ->
if (!hasGroupBeenCreated) return@Observer
sharedInformationViewModel.value.groupCreatedFlag.value = false
Log.d("submitList", "groupCreatedFlag")
initAdapter()
})
}
At some point in my code in a different Fragment that also has an instance of my shared ViewModel, I trigger a value change for my Boolean LiveData -
sharedInformationViewModel.value.groupCreatedFlag.value = true
Which in turn triggers the observer, and does a re-fetch for my group list.
The issue I am facing is that when re-fetching for a new list (because a new group has been added) I do get the current information and everything should work 100% fine, but the new data - the newly created group - does not appear.
The newly added data appears in the list under 2 circumstances -
There is one exception to this issue - if the group list is empty, the first group to be added does indeed appear when I submit the list with one group.
What is it that I am missing?
Edit -
Here is my adapter.
I am using a custom call called DefaultAdapterDiffUtilCallback, which expects a model that implements an interface that defines the unique ID for each model so I can compare new and old models.
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
}
edit 2.0 -
my observeOnce()
extension -
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)
}
})
}
I figured out the problem.
And it has nothing to do with my fetch logics.
The issue is the following -
When creating a group, I am adding a new Fragment to the backstack and popping it off when completed.
When deleting a group, I am navigating forward to the main Fragment of mine while using popUpTo
and popUpToInclusive
- that works fine.
I needed to use the navigation rather than popping backwards the stack in order to see the new list.
This took me 3 days of work to figure out. jeez