0

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?

Zoe
  • 27,060
  • 21
  • 118
  • 148
Foobar
  • 7,458
  • 16
  • 81
  • 161
  • Can I ask you what the purpose is of the code in `onBindViewHolder` is? Are you trying to make sure that image load one by one and not all together? – Napster Jun 08 '18 at 01:41
  • Yes. I am trying to only add another item to my recycler view, after the previous `ImageView`in the recycler view has finished loading an image. I updated my post with more explanation. – Foobar Jun 08 '18 at 01:42
  • how about posting a runnable on the main thread with a delay? – tompee Jun 08 '18 at 01:57
  • @tompee I managed to fix the problem by adding a 1 millisecond delay using a coroutine- my problem with this solution is that it seems very inconsistent. For example, what if on a slower phone, a 1 millisecond delay isn't enough? – Foobar Jun 08 '18 at 02:10
  • But coroutines run in parallel with the main thread, which introduces your concern. But I am talking about posting a runnable on the main thread. I am not sure the delay is even needed but just for good measure. Check this out https://stackoverflow.com/questions/21937224/does-posting-runnable-to-an-ui-thread-guarantee-that-layout-is-finished-when-it – tompee Jun 08 '18 at 02:25
  • I used your solution, and it worked. I'm going to post an answer with some code. Can you verify that what I am doing is correct? – Foobar Jun 08 '18 at 02:51

2 Answers2

3

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.

Foobar
  • 7,458
  • 16
  • 81
  • 161
0

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)
Yosi Pramajaya
  • 3,895
  • 2
  • 12
  • 34
  • I can't use `notifyDataSetChanged` after all the URLs have been loaded because I want to load one URL at a time. Also, can I use `notifyItemChanged` after adding a new item to recycler view, I thought it was only used when modifying an existing item? – Foobar Jun 08 '18 at 11:32
  • You can use `notifyItemInserted(position)` to notify when there's any insertion – Yosi Pramajaya Jun 08 '18 at 13:20