Bug 19335 - Can't remove event handler from ViewTreeObserver
Summary: Can't remove event handler from ViewTreeObserver
Alias: None
Product: Android
Classification: Xamarin
Component: Bindings ()
Version: 4.12.2
Hardware: PC Windows
: Normal normal
Target Milestone: ---
Assignee: Atsushi Eno
Depends on:
Reported: 2014-04-28 07:02 UTC by Miha Markic
Modified: 2014-09-11 12:31 UTC (History)
5 users (show)

Is this bug a regression?: ---
Last known good build:

Sample project (10.63 KB, application/rar)
2014-04-28 07:02 UTC, Miha Markic

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.

Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:

Description Miha Markic 2014-04-28 07:02:16 UTC
Created attachment 6662 [details]
Sample project

If I put this piece of code in OnResume (default Xamarin template)

System.Diagnostics.Debug.WriteLine("On resume size is {0} on observer {1}", button.Width, button.ViewTreeObserver.GetHashCode());
button.ViewTreeObserver.GlobalLayout += ViewTreeObserver_GlobalLayout;

and implement the event like:

void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
            ViewTreeObserver vto = (ViewTreeObserver)sender;
            vto.GlobalLayout -= ViewTreeObserver_GlobalLayout;
            System.Diagnostics.Debug.WriteLine("Global layout {0} sender", sender.GetHashCode());
            button.ViewTreeObserver.GlobalLayout -= ViewTreeObserver_GlobalLayout;
            System.Diagnostics.Debug.WriteLine("Global layout {0} on observer {1}", button.Width, button.ViewTreeObserver.GetHashCode());

I see that within the event the button.ViewTreeObserver isn't same as the original. Thus I can't remove the event from it.
However, sender is the original ViewTreeObserver, at least it looks so from the Hashcode. But unsubscribing from it causes Java.Lang.IllegalStateException.

TL;DR can't unsubscribe from button.ViewTreeObserver.GlobalLayout.
Comment 1 Udham Singh 2014-04-29 08:48:45 UTC
I have checked this issue with Sample project attached in bug description. As per my understanding, I have noticed things mentioned below:

1. In event the value of "button.ViewTreeObserver.GetHashCode()" is not same as the original(method OnResume ()).
2. I am getting "Java.Lang.IllegalStateException" exception. On commenting "vto.GlobalLayout -= ViewTreeObserver_GlobalLayout;" event, problem goes away.

Screencast : http://www.screencast.com/t/KpanCE7Kj

Environment Info : 

Windows 6.2.9200.0 (64-bit)
Xamarin Studio: 4.2.3 (build 60)
Xamarin.Android : 4.12.2 (Business Edition)

Please let me know if I am on different page to very this issue.
Comment 2 Miha Markic 2014-04-29 08:54:04 UTC
Hi Udham,

Yes, that's why I am seeing, too.
You can't unsubscribe from the original ViewTreeObserver (Java.Lang.IllegalStateException) and you can't unsubscribe from button's ViewTreeObserver because is a different isntance.
Comment 3 Udham Singh 2014-04-29 09:25:06 UTC
Supplement Info:

Exception Log : https://gist.github.com/Shruti360/232bfa8a246434202485
Ide Log : https://gist.github.com/Shruti360/0bc0529320c4f0d2768e

Environment Info : 

Windows 6.2.9200.0 (64-bit)
Xamarin Studio: 4.2.3 (build 60)
Xamarin.Android : 4.12.2 (Business Edition)
Comment 4 Danyal Aytekin 2014-05-20 09:30:58 UTC
This bug also affects ViewTreeObserver.PreDraw.
Comment 5 Atsushi Eno 2014-08-21 03:09:56 UTC
From what I have observed, ViewTreeObserver_GlobalLayout() is invoked twice while OnResume() is invoked only once. That is, the event handler is added only once and it is attempted to remove the event handler twice.
That looks like a bug in the application so please verify your event registration hook is in expected manner.

With the same logic, it would happen to *any* event such as PreDraw.
Comment 6 Horácio J. C. Filho 2014-09-10 01:14:07 UTC
Hi guys, 

May you try to use this workaround https://gist.github.com/HoracioFilho/da63a8606b030094a35e? :D :D :D :D 

Thanks in advance
Comment 7 Atsushi Eno 2014-09-11 04:50:02 UTC
Hi, I don't think this "workaround" applies here, as it is not really only about GlobalLayout but also about any event such as PreDraw.
Comment 8 Jonathan Pryor 2014-09-11 12:31:43 UTC
> TL;DR can't unsubscribe from button.ViewTreeObserver.GlobalLayout.

Not quite. You can unsubscribe from button.ViewTreeObserver.GlobalLayout, but you CANNOT always do it from ViewTreeObserver_GlobalLayout().

For example, if you unsubscribe within OnResume(), the app doesn't blow up. (Yes, ViewTreeObserver_GlobalLayout() won't be invoked either, but this demonstrates that the issue is not the event unsubscription in and of itself.)

What's the actual/original error?

> I/MonoDroid( 6281): java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
> I/MonoDroid( 6281):     at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:720)
> I/MonoDroid( 6281):     at android.view.ViewTreeObserver.removeOnGlobalLayoutListener(ViewTreeObserver.java:529)
> I/MonoDroid( 6281):     at mono.android.view.ViewTreeObserver_OnGlobalLayoutListenerImplementor.n_onGlobalLayout(Native Method)
> I/MonoDroid( 6281):     at mono.android.view.ViewTreeObserver_OnGlobalLayoutListenerImplementor.onGlobalLayout(ViewTreeObserver_OnGlobalLayoutListenerImplementor.java:29)
> I/MonoDroid( 6281):     at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:815)
> ...

As mentioned in Comment #0, the app dies because of an IllegalStateException, because this ViewTreeObserver instance "is not alive".

The docs actually note that the instance can be invalidated: http://developer.android.com/reference/android/view/View.html#getViewTreeObserver()

> The returned ViewTreeObserver observer is not guaranteed to remain 
> valid for the lifetime of this View. If the caller of this method keeps a 
> long-lived reference to ViewTreeObserver, it should always check for 
> the return value of isAlive().

The Button.ViewTreeObserver instance is being invalidated, and thus the instance cannot be used at all, which means you can't unsubscribe from its events.

Consequently, you should change ViewTreeObserver_GlobalLayout() to take this into consideration, and only unsubscribe if vto.IsAlive is true:

>        void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
>        {
>            System.Diagnostics.Debug.WriteLine("Global layout {0} sender", sender.GetHashCode());
>            ViewTreeObserver vto = (ViewTreeObserver)sender;
>            if (vto.IsAlive)
>                vto.GlobalLayout -= ViewTreeObserver_GlobalLayout;
>            System.Diagnostics.Debug.WriteLine("Global layout {0} on observer {1}", button.Width, button.ViewTreeObserver.GetHashCode());
>        }