Bug 17257 - NoSuchMethodError when using TreeViewObserver.GlobalLayout < API level 16
Summary: NoSuchMethodError when using TreeViewObserver.GlobalLayout < API level 16
Status: REOPENED
Alias: None
Product: Android
Classification: Xamarin
Component: Bindings (show other bugs)
Version: 4.10.1
Hardware: PC Mac OS
: Highest normal
Target Milestone: master
Assignee: dean.ellis
URL:
Depends on:
Blocks:
 
Reported: 2014-01-15 15:22 UTC by Steve Gordon
Modified: 2017-03-21 20:21 UTC (History)
8 users (show)

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

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 for Bug 17257 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
REOPENED

Description Steve Gordon 2014-01-15 15:22:31 UTC
When detaching an event handler from the TreeViewObserver.GlobalLayout event, a NoSuchMethodError exception occurs when an app is running on < API level 16.

This is caused because the handler calls into RemoveOnGlobalLayoutListener(), which was introduced in API level 16. It appears that prior to that version, apps were using RemoveGlobalOnLayoutListener() which didn't adhere to the naming convention used by the rest of the methods in the class. The latter name has been marked as deprecated.

The code should use RemoveGlobalOnLayoutListener() when < 16 and RemoveOnGlobalLayoutListener() when >= 16.

The only workaround is to implement your own listener, which is significantly more complicated than the .NET event syntax.
Comment 1 Steve Gordon 2014-01-15 15:23:21 UTC
Stack trace:

[MonoDroid] UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
[MonoDroid] at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) 
[MonoDroid] at Android.Views.ViewTreeObserver.RemoveOnGlobalLayoutListener (Android.Views.ViewTreeObserver/IOnGlobalLayoutListener) 
[MonoDroid] at Java.Interop.EventHelper.RemoveEventHandler (System.WeakReference&,System.Func2<Android.Views.ViewTreeObserver/IOnGlobalLayoutListenerImplementor, bool>,System.Action1,System.Action`1) 
[MonoDroid] at Android.Views.ViewTreeObserver.remove_GlobalLayout (System.EventHandler) 
[MonoDroid] at Mobilligy.Controls.NavigationMenu.OnDisposed () 
[MonoDroid] at Mobilligy.Controls.Control.Dispose () 
[MonoDroid] at Mobilligy.Controllers.MobilligyController.OnStop () 
[MonoDroid] at Android.App.Activity.n_OnStop (intptr,intptr) 
[MonoDroid] at (wrapper dynamic-method) object.605db88e-775c-4217-8f04-494f0376847d (intptr,intptr) 
[MonoDroid] 
[MonoDroid] --- End of managed exception stack trace ---
[MonoDroid] java.lang.NoSuchMethodError: no method with name='removeOnGlobalLayoutListener' signature='(Landroid/view/ViewTreeObserver$OnGlobalLayoutListener;)V' in class Landroid/view/ViewTreeObserver;
[MonoDroid] at mobilligy.controllers.MobilligyController.n_onStop(Native Method)
[MonoDroid] at mobilligy.controllers.MobilligyController.onStop(MobilligyController.java:78)
[MonoDroid] at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1174)
[MonoDroid] at android.app.Activity.performStop(Activity.java:4603)
[MonoDroid] at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3071)
[MonoDroid] at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3130)
[MonoDroid] at android.app.ActivityThread.access$1200(ActivityThread.java:123)
[MonoDroid] at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1180)
[MonoDroid] at android.os.Handler.dispatchMessage(Handler.java:99)
[MonoDroid] at android.os.Looper.loop(Looper.java:137)
[MonoDroid] at android.app.ActivityThread.main(ActivityThread.java:4424)
[MonoDroid] at java.lang.reflect.Method.invokeNative(Native Method)
[MonoDroid] at java.lang.reflect.Method.invoke(Method.java:511)
[MonoDroid] at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)
[MonoDroid] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)
[MonoDroid] at dalvik.system.NativeStart.main(Native Method)
Comment 2 Atsushi Eno 2014-01-16 02:02:27 UTC
Hello,

Thanks for the raising the issue.

I had a look at the ViewTreeObserver API and you're right, that would throw that missing method error. That is however, expected. The same happens if you try to use new API in old platforms that do not have corresponding Java mathod. That happens even in Java land. To avoid that "new API is not available in old API" kind of problem, you'll have to add run-time version check:
https://developer.android.com/training/basics/supporting-devices/platforms.html#version-codes

Here comes another question: but ViewTreeObserver has GlobalLayout event even in older API! Why does that exist?

The answer lies in the "remove" implementation. If you try to remove an event handler from GlobalLayout, it throws NotSupportedException. Usually it does not happen, but for ViewTreeObserver.GlobalLayout it does, because, as you discovered , removeOnGlobalLayoutListener() does NOT exist, and Xamarin.Android event framework expects removeOnXxxListener method. Wherever the Android API does not conform to our expectation, the remove method throws NotSupportedException (that add-only events would be still helpful for some use-cases). Therefore, this event can add handlers, but cannot remove them.

The next best way to use the listener would be, unfortunately, to do the same as Java. Android.Views.ViewTreeObserver.IOnGlobalLayoutListener exists, so do ViewTreeObserver.[Add|Remove]OnGlobalLayoutListener() methods.
Comment 3 Steve Gordon 2014-01-16 02:23:37 UTC
I believe that you have misunderstood the problem. The "incorrectly" named RemoveGlobalOnLayoutListener() has existed since API level 1. Google renamed this API to RemoveOnGlobalLayoutListener() in API level 16 to match the naming convention used by other methods in the class and deprecated the earlier method.

The Xamarin binding is using the newer method, irrespective of what version of the platform the app is running on. I expect the GlobalLayout event to call the appropriately named method pre-level 16 and post-level 16 so that when I detach the event, it works as expected.

Essentially, in the remove handler, do the following:

public event EventHandler GlobalLayout {
   add { ... }
   remove {
       if (level < 16) {
           RemoveGlobalOnLayoutListener();
       }
       else {
           RemoveOnGlobalLayoutListener();
       }
   }
}

As the consumer of the .NET event, I don't care about the underlying rename that happened in the Android platform. I want the event detached without an exception being thrown.

As I mentioned above, the only workaround that exists is to not use the GlobalLayout event at all and instead use the listener methods directly, which is undesirable given the additional complexity this brings compared to the .NET event syntax.
Comment 4 Atsushi Eno 2014-01-16 03:09:06 UTC
Please note this statement in comment #2:

> Wherever the Android API
does not conform to our expectation, the remove method throws
NotSupportedException (that add-only events would be still helpful for some
use-cases).

No matter how developer human beings can see the problem obviously, invoking the "corresponding" Java add/remove methods is not a manual process at all. Not very often, but this kind of framework-level error is inevitable, and hence such a mismatch will happen.
Comment 5 David Schwegler 2014-04-17 19:52:56 UTC
The Xamarin implementation of this event is unusable for application developers supporting < API 16, and leads to subtle crashes.

I acknowledge what you're saying that it may be difficult to fix, but I agree with Steve that this is a bug, not a feature. 

If nothing else, it would be helpful to document this, so developers know that it will crash < API 16.
Comment 6 Atsushi Eno 2014-04-18 01:46:04 UTC
Our documentation EXPLICITLY mentions that this event is available only API Level 16 or later.
Comment 7 Atsushi Eno 2014-04-18 01:49:41 UTC
Sorry that was wrong (actually I noticed while I was writing this but ENTER key hit before I was altering text, stupid me). The only FIX can be just removing the API. But I'm sure no one wants that.

Our documentation page doesn't provide any API Level information. There could be chance for enhancements.
Comment 8 Horácio J. C. Filho 2014-09-10 01:16:22 UTC
Hi guys, 

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

Thanks in advance
Comment 9 Horácio J. C. Filho 2014-09-11 13:23:44 UTC
Hello,

I think this bug could be fixed with the above workaround, sorry for my mistakes. The ViewTreeObserver class binding need manual adjusts around GlobalLayout event and it would work nice and backwards compatible.
Comment 10 Horácio J. C. Filho 2014-09-11 13:29:00 UTC
On GlobalLayout event:
[...]
remove
            {
                EventHelper.RemoveEventHandler<ViewTreeObserver.IOnGlobalLayoutListener, OnGlobalLayoutListenerImpl>(
                    ref _implementor,
                    implementor => implementor.Handler == null,
                    listener =>
                    {
                        > Here is the solution
                        if (Build.VERSION.SdkInt < BuildVersionCodes.JellyBean)
                        {
                            _observer.RemoveGlobalOnLayoutListener(listener);
                        }
                        else
                        {
                            _observer.RemoveOnGlobalLayoutListener(listener);
                        }
                    },
                    implementor => implementor.Handler -= value);
            }
Comment 11 Atsushi Eno 2014-09-12 10:09:24 UTC
Fixed. We have removed generated event code for GlobalLayout and added manual implementation for this, making changes to our API generator. Thanks for the inputs.
Comment 12 Horácio J. C. Filho 2014-09-12 19:03:23 UTC
Thanks a lot :D I'm very excited for see it :D This fix will be available in next release? :D
Comment 14 Atsushi Eno 2014-11-26 08:43:17 UTC
I don't have repro now. But your screenshot doesn't make sense anyways, because it is not about whether the event exists or not but whether it works on each API Level mentioned here.
So, create some app, use the mentioned API and see if it works.
Comment 16 Jonathan Pryor 2015-05-05 17:33:03 UTC
@Ram: The test also needs to check that the event is *raised*, which is NOT the case; see Bug #29730.

Comment #11 is thus an incomplete fix: no exception is raised, but the event isn't raised either, which is arguably more broken than before...

Closing this as a DUPE of Bug #29730, as that contains a description for a more complete fix.

*** This bug has been marked as a duplicate of bug 29730 ***
Comment 17 Jonathan Pryor 2015-05-08 10:13:39 UTC
Reopening, because Bug #29730 was "fixed" by reverting the fix from Comment #11, meaning this bug is now open again.