Foobar
Foobar

Reputation: 8487

How to avoid IllegalStateException When calling notifyDataSetChanged?

I have a horizontal recycler view called imageRecyclerView where each column consists solely of one ImageView. The adapter class for the recycler view is called ImageAdapter. The dataset of imageRecyclerView's adapter is an ArrayList of strings that contain URLs. The ArrayList is called currentImageURLs.

Note - the purpose of all this code is to load images into imageRecyclerView one at a time.

In the onBindViewHolder method of my ImageAdapter class I have the following code. This code loads an image from the adapter's dataset into an ImageView. The code also waits until the image has been loaded into the ImageView before calling recursivelyLoadURLs to add a new item to the adapter's dataset.

    Glide.with(context)
            //items[[position] is a string that contains a URL 
            .load(items[position])
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                    holder.progressBar.visibility = View.GONE
                    context.recursivelyLoadURLs()
                    return false
                }

                override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                    holder.progressBar.visibility = View.GONE
                    context.recursivelyLoadURLs()
                    return false
                }

            })
            .apply(RequestOptions()
                    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC))
            .into(holder.imageView)

The method recursivelyLoadURLs is in the parent Activity class. In essence, what this method does is

1] add another URL to imageRecyclerView's adapter's dataset and

2] call notifyDataSetChanged so imageRecyclerView updates:

fun recursivelyLoadURLs() {


    if (currentImageURLs.size >= targetImageURLs.size) {
        return
    }

    currentImageURLs.add(targetImageURLs[currentImageURLs.size])

    //The code crashes on this line
    mainview.imageRecyclerView.adapter.notifyDataSetChanged()


}

However, the code is crashing with the exception java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling on the line labeled in the above code sample.

I suspect this is happening because imageRecyclerView is still computing a layout when notifyDataSetChanged is being called.

How can I delay calling context.recursivelyLoadURLs until the layout has finished being computed?

Or, how can I add a delay inside recursivelyLoadURLs, so that notifyDataSetChanged is only called once imageRecyclerView has finished computing its new layout?

Upvotes: 0

Views: 1911

Answers (2)

Foobar
Foobar

Reputation: 8487

tompee has suggested posting a Runnable in order to guarantee the recycler view has layed out its children before running notifyDataSetChanged.

As such, I replaced notifyDataSetChanged with

    mainview.imageRecyclerView.post({
        mainview.imageRecyclerView.adapter.notifyDataSetChanged()
    })

and this solved the problem.

Upvotes: 3

Yosi Pramajaya
Yosi Pramajaya

Reputation: 4085

You can read the documentation on RecyclerView.Adapter.notifyDataSetChanged

This event does not specify what about the data set has changed, forcing any observers to assume that all existing items and structure may no longer be valid. LayoutManagers will be forced to fully rebind and relayout all visible views.

If you are writing an adapter it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort.

Here's some tips that might help *hope it helps :)

  1. Try not to use the notifyDataSetChanged(), especially in your onBindViewHolder. Use notifyItemChanged(position) instead. It's much more efficient and cost less memory.
  2. If you really need to use notifyDataSetChanged(), use it AFTER you are ready with ALL of your data (maybe have it when currentImageURLs.size >= targetImageURLs.size)

Upvotes: 0

Related Questions