Reputation: 335
I'm using Paging 3 with flows to populate my RecyclerView, I click on an item and go to a details page and upvote the item, this adds 1 to a counter visible in the ViewHolder.
Everything populates correctly and I've refreshes when I come back to the home screen from the details page, but the item and some others flicker when updating with the data.
My problem is when coming back the item that I've upvoted flickers completely when updating, usually with a recylerview adapter I'd set stable IDs to true, but you can't do that with a PagingDataAdapter, I thought it may be something to do with Glide but the entire ViewHolder refreshes and some others within my list do too.
My DiffUtil for the PagingDataAdapter uses objects.equals which I override in my entry item with only fields that aren't likely to randomly change without user input, so I don't think that's the issue. (Although, other items refreshing in my view may mean this could be at fault?
The viewholders XML -
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="entry"
type="com.model.Entry" />
</data>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:cardCornerRadius="@dimen/rounded_corners"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_entry_item"
android:layout_width="160dp"
android:layout_height="180dp"
android:scaleType="centerCrop"
app:image_from_url="@{entry.imageUrl}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_votes"
style="@style/Text.RH.Body1.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_medium"
android:paddingEnd="@dimen/padding_xsmall"
android:paddingBottom="@dimen/padding_medium"
android:text="@{String.valueOf(entry.voteCount)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/img_entry_item"
tools:text="3000" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_votes_suffix"
style="@style/Text.RH.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/none"
android:paddingEnd="@dimen/padding_medium"
android:text="@string/votes"
app:layout_constraintBaseline_toBaselineOf="@id/text_votes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/text_votes"
app:layout_constraintTop_toBottomOf="@id/img_entry_item" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_index"
style="@style/Text.RH.Body1.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/item_label_background"
android:padding="@dimen/padding_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
My binding adapter for the image load
@BindingAdapter("image_from_url")
fun AppCompatImageView.loadImageWithGlide(url: String?) {
try {
if(this.tag == url) return
this.tag = url
when {
url.isNullOrBlank() -> this.setImageDrawable(null)
else -> {
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.transition(DrawableTransitionOptions.withCrossFade())
.placeholder(CircularProgressDrawable(this.context).apply {
strokeWidth = 5f
centerRadius = 30f
start()
})
.into(this)
}
}
} catch (ex: Exception) {
Timber.e(ex, "Unable to load rounded image url: $url")
}
}
and this is populated from my ViewHolder using PagingDataAdapter
class EntryAdapter(
private val maxItems: Int? = null,
private val onEntryClicked: (entry: Entry) -> (Unit),
) : PagingDataAdapter<Entry, EntryAdapter.ViewHolder>(
DiffItemCallback()
) {
override fun getItemCount(): Int = maxItems?.let { super.getItemCount().coerceAtMost(maxItems) } ?: super.getItemCount()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
ItemEntryItemVotesBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(
entry = getItem(position),
adapterPosition = position
)
}
inner class ViewHolder(
private val binding: ItemEntryItemVotesBinding
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
getItem(absoluteAdapterPosition)?.let(onEntryClicked)
}
}
fun bind(entry: Entry?, adapterPosition: Int) {
binding.entry = entry
binding.textIndex.text = (adapterPosition + 1).toString()
}
}
}
Which is populated from my fragment using Flows
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initDataBinding()
initEntriesRecyclerView()
}
...
private fun initEntriesRecyclerView() {
binding.rvEntries.apply {
adapter = rvAdapter
setSnapToStart()
}
viewModel.competition.observe(
viewLifecycleOwner
) { competition ->
competition?.let {
lifecycleScope.launchWhenResumed {
viewModel.getEntries(
competitionId = competition.id
).collectLatest {
if (entryAdapter.itemCount == 0) {
binding.rvAdapter.layoutAnimation = AnimationUtils.loadLayoutAnimation(
requireContext(),
R.anim.layout_animation_from_right
)
}
rvAdapter.submitData(it)
}
}
}
}
}
and my viewModel
fun getEntries(competitionId: Long) = entryRepository.getEntries(
competitionId = competitionId,
sortBy = EntrySortBy.Votes,
sortDirection = EntrySortDirection.Desc,
scope = viewModelScope
)
then repository
override fun getEntries(
competitionId: Long,
sortBy: EntrySortBy,
sortDirection: EntrySortDirection,
scope: CoroutineScope
) = Pager(
PagingConfig(pageSize = 20)
) {
EntryDataSource(
competitionId = competitionId,
sortBy = sortBy,
sortDirection = sortDirection,
entryRepository = this
)
}.flow.cachedIn(scope)
Upvotes: 2
Views: 1553
Reputation: 404
Just try this -> override in DiffUtil.ItemCallback getChangePayload fun like this↓
override fun getChangePayload(oldItem: ChatsModel, newItem: ChatsModel): Any = Any()
In your case, fun must return "Any()".
Upvotes: 1