Reputation: 43
I know there have been many questions regarding this topic, but I still could not find a solution.
I have an application, that loads and shows images (just like a regular gallery app on a smartphone)
I load all the images in the background with coroutines - no problem at this stage of the process.
The problem occurs when I notify my adapter about the data. notifyDataSetChanged() call freezes the UI for a couple of moments.
We are talking about relatively large grids (1000 + photos for example)
Almost all the answers in other threads suggest, that I should check for which items changed and call notifyItemChanged(index) rather than call notifyDataSetChanged() or use DiffUtil or something similar. I understand all of this, but at the RecyclerView initialization (when the application loads) there is no existing data, so I don't see other options as calling notifyDataSetChanged().
Adapter code:
class PhotosAdapter(private val mFragment: PhotosFragment, private val mListener: ClickListener) : RecyclerView.Adapter<PhotoViewHolder>() {
private var photos: List<PhotoFile>? = null
init {
mFragment.mPhotosViewModel.photos.observe(mFragment, Observer {
it?.let {
photos = it
notifyDataSetChanged()
}
})
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder {
return PhotoViewHolder.from(parent)
}
override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) = holder.bind(
position, photos!!.get(position), mFragment, mListener
)
override fun getItemCount(): Int = photos?.size ?: 0
}
class PhotoViewHolder private constructor(private val mBinding: PhotoGridItemBinding) : RecyclerView.ViewHolder(mBinding.root) {
companion object {
fun from(parent: ViewGroup) : PhotoViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = PhotoGridItemBinding.inflate(layoutInflater, parent, false)
return PhotoViewHolder(binding)
}
}
fun bind(index: Int, photo: PhotoFile, photosFragment: PhotosFragment, listener: ClickListener) {
mBinding.index = index
mBinding.photo = photo
mBinding.viewHolder = this
mBinding.listener = listener
Glide.with(photosFragment).load(photo.uri)
.thumbnail(0.1f).into(mBinding.photoGridItemThumbnail)
mBinding.executePendingBindings()
}
}
Regards, L
Upvotes: 0
Views: 2121
Reputation: 27226
I believe your architecture is missing the final bit.
You said:
"at the RecyclerView initialization (when the application loads) there is no existing data, so I don't see other options as calling notifyDataSetChanged()"
I'm not 100% sure I understand what you mean, but I believe what you're experiencing is:
LiveData<List<T>>
or similar from your ViewModel.List<T>
with your adapter's data.List<T>
from your adapter, you set it via some method you added like adapter.setData(list)
and then call adapter.notifyDataSetChanged()
.And this takes forever.
The reason why other threads were recommending you use DiffUtil()
(or its AsyncDiffUtil
version) is because this helps the Adapter with the ... well.. adapting of your data to a View (holder), by calculating what needs to be adapter and what is the same.
We have not seen (so far) your Adapter, but my Guess is you're extending RecyclerView.Adapter<T>
. This means you ought to do your own DiffUtil (or Async).
Alternatively, You can instead extend ListAdapter<T, K>
where T
is your data type (List<XXX>
) and K
is your ViewHolder type, for which you can use RecyclerView.ViewHolder
if I correctly recall (and then have your ViewHolders Extend that).
This list adapter forces you to supply a DiffUtil in the constructor.
just chechking for differences between two (empty) lists makes no sense
Not really, you're making a problem where the Framework is already offering you a solution. DiffUtil will not be slower (minus a few ms?) if the lists are empty, since it will have nothing to compare. But when there IS data, it will do the heavy lifting for you.
IN any case, until you post some of your code, we're all talking on thin air. :)
Upvotes: 1