1

I'm writing a code to save a bitmap from the app's screen at a specific moment.

I'm doing this by listening to onDraw() events for some component and checking if a set of conditions is true

     @Override
     public void onDraw() {



        if (checkConditions()) {
                            Canvas canv = new Canvas(tmpBitmap);
                            canvasView.draw(canv);
                            saveBitmapToImage(tmpBitmap, Bitmap.CompressFormat.JPEG);
                            this.lastDraw = System.currentTimeMillis();

                            AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
                            viewKonfetti.getViewTreeObserver().removeOnDrawListener(listener);

                        });                   
    }

                    }

But sometimes I'm getting an exception when removing the listener

    Fatal Exception: java.lang.IllegalStateException: Cannot call removeOnDrawListener inside of onDraw
           at android.view.ViewTreeObserver.removeOnDrawListener(ViewTreeObserver.java:736)
           at com.tomatedigital.lottogram.dialogs.ShuffleWinnerDialog$Shuffler$2.lambda$onParticleSystemEnded$1(ShuffleWinnerDialog.java:235)
           at com.tomatedigital.lottogram.dialogs.-$$Lambda$ShuffleWinnerDialog$Shuffler$2$vCYJiRVhO65xXIsicqZHHpw_34A.run(-.java:4)
           at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
           at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
           at java.lang.Thread.run(Thread.java:764)

This doesn't happen always, it happens sometimes and in production (on many different android devices). BUT WHY?

I'm even using a new thread to remove the listener...

I tested, and even removing the new thread does not solve the issue: this error doesn't occur 100% of the times, just sometimes.

What is the explanation? how to solve it?

Yennefer
  • 5,704
  • 7
  • 31
  • 44
Rafael Lima
  • 3,079
  • 3
  • 41
  • 105

1 Answers1

1

"An OnDrawListener listener cannot be added or removed from this method." This is in the documentation, and based on your experience, the cause might be some kind of race condition, but I don't know details.

A workaround I found is the following: create a GlobalLayoutListener, and remove the onDrawListener there. Be careful, because the GlobalLayoutListner will be called before and after onDraw a lot of times. Here is my code (which is not perfect, because GlobalLayoutListener will be called a couple of times after isDescriptionListenerAdded set to false because of the different threads, but it is not critical. Feel free to improve it.)

    private var isDescriptionListenerAdded = false

fun setText(title: String, description: String, note: String) {
    titleText.text = title
    descriptionText.text = description

    var descriptionDrawListener = ViewTreeObserver.OnDrawListener {
        val maxLines = descriptionText.height / descriptionText.lineHeight
        descriptionText.maxLines = maxLines
        isDescriptionListenerAdded = true
    }

    descriptionText.viewTreeObserver.addOnDrawListener(descriptionDrawListener)
    descriptionText.viewTreeObserver.addOnGlobalLayoutListener {
        if (isDescriptionListenerAdded) {
            isDescriptionListenerAdded = false
            descriptionText.viewTreeObserver.removeOnDrawListener(descriptionDrawListener)
        }
    }
}

As you can see, after the text is set, it calculates how many lines can be shown entirely, and sets the maxLines to that value. After it is done, isDescriptionListenerAdded is set to true, so in GlobalLayoutListener it will be removed, and it is set to false, so it will never run again.

user2424380
  • 1,393
  • 3
  • 16
  • 29