Bug 32811 - Tabbed pages freezing
Summary: Tabbed pages freezing
Status: VERIFIED ANSWERED
Alias: None
Product: Android
Classification: Xamarin
Component: Mono runtime / AOT Compiler ()
Version: unspecified
Hardware: All All
: Normal normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2015-08-05 19:02 UTC by David
Modified: 2015-08-27 19:26 UTC (History)
2 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 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:
Status:
VERIFIED ANSWERED

Description David 2015-08-05 19:02:06 UTC
I have an error that is frustratingly hard to reproduce and difficult to debug. I have a tabbed view and changing between tabs suddenly freezes the application (non-deterministically, sometimes first tab change, sometimes much later). I use Insights and this is what is thrown:

Message:
0object.079057a8-cb67-4d29-b636-815209fff6f0 (intptr,intptr,intptr,intptr)
Stack Trace:
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
Android.Runtime.JNIEnv.CallObjectMethod
Android.App.FragmentTransactionInvoker.Add(int,Android.App.Fragment)
MyApp.Droid.ViewRecipeActivity/c__AnonStorey0.<>m__0
Android.App.TabEventDispatcher.OnTabSelected

----------- complete stack trace ----------
t System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () <0x00028>
at Android.Runtime.JNIEnv.CallObjectMethod (intptr,intptr,Android.Runtime.JValue*) <0x000c3>
at Android.App.FragmentTransactionInvoker.Add (int,Android.App.Fragment) <0x00193>
at MyApp.Droid.ViewRecipeActivity/c__AnonStorey0.<>m__0 (object,Android.App.ActionBar/TabEventArgs) <0x00093>
at Android.App.TabEventDispatcher.OnTabSelected (Android.App.ActionBar/Tab,Android.App.FragmentTransaction) <0x0005f>
at Android.App.ActionBar/ITabListenerInvoker.n_OnTabSelected_Landroid_app_ActionBar_Tab_Landroid_app_FragmentTransaction_ (intptr,intptr,intptr,intptr) <0x0007f>
at (wrapper dynamic-method) object.079057a8-cb67-4d29-b636-815209fff6f0 (intptr,intptr,intptr,intptr) <0x0004b>

--- End of managed exception stack trace ---
java.lang.NullPointerException
at android.app.BackStackRecord.doAddOp(BackStackRecord.java:352)
at android.app.BackStackRecord.add(BackStackRecord.java:342)
at mono.android.app.TabEventDispatcher.n_onTabSelected(Native Method)
at mono.android.app.TabEventDispatcher.onTabSelected(TabEventDispatcher.java:39)
at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:573)
at com.android.internal.app.ActionBarImpl$TabImpl.select(ActionBarImpl.java:1075)
at com.android.internal.widget.ScrollingTabContainerView$TabClickListener.onClick(ScrollingTabContainerView.java:489)
at android.view.View.performClick(View.java:4100)
at android.view.View$PerformClick.run(View.java:17016)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4838)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:875)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:642)
at dalvik.system.NativeStart.main(Native Method)

That is outside of my code and I don't know how to fix that.

Using:
Xamarin Studio 5.9.4 (build 5)
Mono 4.0.2
Xamarin Android 5.1.4.16

Tested on phone with API level 16. I can NOT make the same thing happen on the Android Player emulator with the same or any other API level.
Comment 1 Jonathan Pryor 2015-08-06 11:07:01 UTC
Based on the Java stack trace:

> java.lang.NullPointerException
> at android.app.BackStackRecord.doAddOp(BackStackRecord.java:352)
> at android.app.BackStackRecord.add(BackStackRecord.java:342)

You're *probably* hitting this:

https://github.com/android/platform_frameworks_support/blob/jb-mr0-release/v4/java/android/support/v4/app/BackStackRecord.java#L352

and `fragment` is null, so `fragment.mFragmentManager = mManager` triggers the NullPointerException.

Walking up the stack:

https://github.com/android/platform_frameworks_support/blob/jb-mr0-release/v4/java/android/support/v4/app/BackStackRecord.java#L342

which is the public add(int, Fragment) method, but then things get "weird":

> at mono.android.app.TabEventDispatcher.n_onTabSelected(Native Method)

How is an Android Callable Wrapper calling BackStackRecord.add()?

The answer is that BackStackRecord extends FragmentTransaction, which is public, and that TabEventDispatcher is in charge of raising the ActionBar.Tab.TabSelected event:

http://developer.xamarin.com/api/event/Android.App.ActionBar+Tab.TabSelected

So what's presumably happening is that your code has subscribed to the ActionBar.Tab.TabSelected event, which when raised calls FragmentTransaction.Add(int, Fragment), and passes a null Fragment instance, which promptly causes BackStackRecord.add(int, Fragment) to (eventually) throw.

Assuming that's the case, you need to ensure that you don't pass `null` as a Fragment value to FragmentTransaction.Add(int, Fragment). Please audit your code.
Comment 2 David 2015-08-06 15:42:22 UTC
Hi Jonathan,

Thanks for the in depth analysis.

You are right that I'm subscribing to the TabSelected event. My code seems really straight forward, very much in sync with the sample project from here:http://developer.xamarin.com/guides/android/user_interface/tab_layout/actionbar/

This is the activity that creates  the tabs.

                 protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);

			ActionBar.SetDisplayShowTitleEnabled (true);
			ActionBar.SetDisplayHomeAsUpEnabled(true);

			ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
			SetContentView(Resource.Layout.main);

			AddTab ("a", Resource.Drawable.Nutrition, new NutritionTabFragment (this));
			AddTab ("b", Resource.Drawable.Nutrition, new RecipeTabFragment (this));
		        // ... more tabs

			Window.SetTitle (recipe.Title);
		}

		void AddTab (string tabText, int iconResourceId, Fragment fragment)
		{
			var tab = this.ActionBar.NewTab();            
			tab.SetIcon (iconResourceId);

			// must set event handler before adding tab
			tab.TabSelected += delegate(object sender, ActionBar.TabEventArgs e)
			{
				e.FragmentTransaction.Replace(Resource.Id.frameLayout, fragment);

			};
			
			this.ActionBar.AddTab (tab);
		}

Passing null would mean "fragment" in AddTab would need to be null right? But that can only happen if new NutritionTabFragment or new RecipeTabFragment returned null which they don't.

Can you see something I'm missing here. What about that 'jobject' being null what is that?

--------------------- here again the full stack trace of when it happens -------------------
[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] Java.Lang.RuntimeException: Exception of type 'Java.Lang.RuntimeException' was thrown.
[MonoDroid] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () <0x00028>
[MonoDroid] at Android.Runtime.JNIEnv.CallObjectMethod (intptr,intptr,Android.Runtime.JValue*) <0x000c3>
[MonoDroid] at Android.App.FragmentTransactionInvoker.Replace (int,Android.App.Fragment) <0x00193>
[MonoDroid] at MyApp.Droid.ViewRecipeActivity/<AddTab>c__AnonStorey0.<>m__0 (object,Android.App.ActionBar/TabEventArgs) <0x0003b>
[MonoDroid] at Android.App.TabEventDispatcher.OnTabSelected (Android.App.ActionBar/Tab,Android.App.FragmentTransaction) <0x00067>
[MonoDroid] at Android.App.ActionBar/ITabListenerInvoker.n_OnTabSelected_Landroid_app_ActionBar_Tab_Landroid_app_FragmentTransaction_ (intptr,intptr,intptr,intptr) <0x0007f>
[MonoDroid] at (wrapper dynamic-method) object.3daef3e7-3650-45f4-9eb3-d1af615f58ed (intptr,intptr,intptr,intptr) <0x0004b>
[MonoDroid]   --- End of managed exception stack trace ---
[MonoDroid] java.lang.NullPointerException
[MonoDroid] 	at android.app.BackStackRecord.doAddOp(BackStackRecord.java:352)
[MonoDroid] 	at android.app.BackStackRecord.replace(BackStackRecord.java:387)
[MonoDroid] 	at android.app.BackStackRecord.replace(BackStackRecord.java:379)
[MonoDroid] 	at mono.android.app.TabEventDispatcher.n_onTabSelected(Native Method)
[MonoDroid] 	at mono.android.app.TabEventDispatcher.onTabSelected(TabEventDispatcher.java:39)
[MonoDroid] 	at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:573)
[MonoDroid] 	at com.android.internal.app.ActionBarImpl$TabImpl.select(ActionBarImpl.java:1075)
[MonoDroid] 	at com.android.internal.widget.ScrollingTabContainerView$TabClickListener.onClick(ScrollingTabContainerView.java:489)
[MonoDroid] 	at android.view.View.performClick(View.java:4100)
[MonoDroid] 	at android.view.View$PerformClick.run(View.java:17016)
[MonoDroid] 	at android.os.Handler.handleCallback(Handler.java:615)
[MonoDroid] 	at android.os.Handler.dispatchMessage(Handler.java:92)
[MonoDroid] 	at android.os.Looper.loop(Looper.java:137)
[MonoDroid] 	at android.app.ActivityThread.main(ActivityThread.java:4838)
[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:875)
[MonoDroid] 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:642)
[MonoDroid] 	at dalvik.system.NativeStart.main(Native Method)
[Xamarin.Insights] Warning: Unhandled exception: Java.Lang.RuntimeException: Exception of type 'Java.Lang.RuntimeException' was thrown.
[Xamarin.Insights] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () <0x00028>
[Xamarin.Insights] at Android.Runtime.JNIEnv.CallObjectMethod (intptr,intptr,Android.Runtime.JValue*) <0x000c3>
[Xamarin.Insights] at Android.App.FragmentTransactionInvoker.Replace (int,Android.App.Fragment) <0x00193>
[Xamarin.Insights] at MyApp.Droid.ViewRecipeActivity/<AddTab>c__AnonStorey0.<>m__0 (object,Android.App.ActionBar/TabEventArgs) <0x0003b>
[Xamarin.Insights] at Android.App.TabEventDispatcher.OnTabSelected (Android.App.ActionBar/Tab,Android.App.FragmentTransaction) <0x00067>
[Xamarin.Insights] at Android.App.ActionBar/ITabListenerInvoker.n_OnTabSelected_Landroid_app_ActionBar_Tab_Landroid_app_FragmentTransaction_ (intptr,intptr,intptr,intptr) <0x0007f>
[Xamarin.Insights] at (wrapper dynamic-method) object.3daef3e7-3650-45f4-9eb3-d1af615f58ed (intptr,intptr,intptr,intptr) <0x0004b>
[Xamarin.Insights] 
[Xamarin.Insights]   --- End of managed exception stack trace ---
[Xamarin.Insights] java.lang.NullPointerException
[Xamarin.Insights] 	at android.app.BackStackRecord.doAddOp(BackStackRecord.java:352)
[Xamarin.Insights] 	at android.app.BackStackRecord.replace(BackStackRecord.java:387)
[Xamarin.Insights] 	at android.app.BackStackRecord.replace(BackStackRecord.java:379)
[Xamarin.Insights] 	at mono.android.app.TabEventDispatcher.n_onTabSelected(Native Method)
[Xamarin.Insights] 	at mono.android.app.TabEventDispatcher.onTabSelected(TabEventDispatcher.java:39)
[Xamarin.Insights] 	at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:573)
[Xamarin.Insights] 	at com.android.internal.app.ActionBarImpl$TabImpl.select(ActionBarImpl.java:1075)
[Xamarin.Insights] 	at com.android.internal.widget.ScrollingTabContainerView$TabClickListener.onClick(ScrollingTabContainerView.java:489)
[Xamarin.Insights] 	at android.view.View.performClick(View.java:4100)
[Xamarin.Insights] 	at android.view.View$PerformClick.run(View.java:17016)
[Xamarin.Insights] 	at android.os.Handler.handleCallback(Handler.java:615)
[Xamarin.Insights] 	at android.os.Handler.dispatchMessage(Handler.java:92)
[Xamarin.Insights] 	at android.os.Looper.loop(Looper.java:137)
[Xamarin.Insights] 	at android.app.ActivityThread.main(ActivityThread.java:4838)
[Xamarin.Insights] 	at java.lang.reflect.Method.invokeNative(Native Method)
[Xamarin.Insights] 	at java.lang.reflect.Method.invoke(Method.java:511)
[Xamarin.Insights] 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:875)
[Xamarin.Insights] 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:642)
[Xamarin.Insights] 	at dalvik.system.NativeStart.main(Native Method)
[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] System.ArgumentException: 'jobject' must not be IntPtr.Zero.
[MonoDroid] Parameter name: jobject
[MonoDroid] at Android.Runtime.JNIEnv.CallVoidMethod (intptr,intptr,Android.Runtime.JValue*) <0x0013c>
[MonoDroid] at Java.Lang.Thread/IUncaughtExceptionHandlerInvoker.UncaughtException (Java.Lang.Thread,Java.Lang.Throwable) <0x0013b>
[MonoDroid] at Android.Runtime.UncaughtExceptionHandler.UncaughtException (Java.Lang.Thread,Java.Lang.Throwable) <0x0014f>
[MonoDroid] at Java.Lang.Thread/IUncaughtExceptionHandlerInvoker.n_UncaughtException_Ljava_lang_Thread_Ljava_lang_Throwable_ (intptr,intptr,intptr,intptr) <0x0007f>
[MonoDroid] at (wrapper dynamic-method) object.8c5251c8-50a8-4596-a416-824c8e88dc09 (intptr,intptr,intptr,intptr) <0x0004b>
Comment 3 David 2015-08-06 16:23:13 UTC
Also, if that helps: I cannot reproduce the error when I use "Debug" instead of "Release" and deploy it on the phone. I wanted to set a breakpoint but I never get the crashing behavior. I find this extremely confusing.
Comment 4 Jonathan Pryor 2015-08-06 17:25:49 UTC
Would it be possible for you to provide a repro?
Comment 5 David 2015-08-06 17:52:29 UTC
That's rather difficult as the app and its workflows are attached to user accounts. I would have to work a lot to isolate this code, therefore I'd like to try to find a solution for that differently.

The error really comes from here:

e.FragmentTransaction.Replace(Resource.Id.frameLayout, fragment);

But fragment is never null at this point (I checked with if (fragment == null) Console.Writeln("null")). I don't know what happens in this replace call, is the oncreateview of the fragment called and if something fails there it crashes?

Why could Release make it happen while Debug doesn't?
Comment 6 Jonathan Pryor 2015-08-18 22:44:29 UTC
Aside: you can inhibit line wrapping in bugzilla by prefixing lines with '>'.

From Comment #2:

> Passing null would mean "fragment" in AddTab would need to be null right?

There's an important related bit of information here which I should have mentioned previously: there are two "forms" of null:

1. C# variables which reference `null`
2. Java.Lang.Object instance which have a Handle value of IntPtr.Zero.

When instances of (2) are passed to Java code, a null Java handle is passed as the parameter/etc.

Which brings us to your code:

    void AddTab (string tabText, int iconResourceId, Fragment fragment)
    {
      var tab = this.ActionBar.NewTab();            
      tab.SetIcon (iconResourceId);
    
      // must set event handler before adding tab
      tab.TabSelected += delegate(object sender, ActionBar.TabEventArgs e) {
          e.FragmentTransaction.Replace(Resource.Id.frameLayout, fragment);
      };
    
      this.ActionBar.AddTab (tab);
    }

`fragment` is captured in a closure for the tab.TabSelected event. Consequently, *if* `fragment` is finalized at some point -- because the Java instance was collected, for example, because fragments are "weird" in all manner of ways -- then `fragment.Handle` will be IntPtr.Zero, and thus e.FragmentTransaction.Replace() will be equivalent to:

          e.FragmentTransaction.Replace(Resource.Id.frameLayout, null);

Whether that's actually happening or not, I can't say, but it's certainly a *possibility*.

From Comment #5:

> The error really comes from here:
> e.FragmentTransaction.Replace(Resource.Id.frameLayout, fragment);

I think my "possibility" is rather more likely...

I would suggest updating this code to check the value of fragment.Handle to verify that this is happening.

If that is happening, I'm not sure what the fix is, though I imagine it'll involve tracking the lifetime of the Fragment instances so that you don't retain references to dead instances...

Back to Comment #2:

> What about that 'jobject' being null what is that?
...
> [MonoDroid] UNHANDLED EXCEPTION:
> [MonoDroid] System.ArgumentException: 'jobject' must not be IntPtr.Zero.
> [MonoDroid] Parameter name: jobject
> [MonoDroid] at Android.Runtime.JNIEnv.CallVoidMethod (intptr,intptr,Android.Runtime.JValue*) <0x0013c>
> [MonoDroid] at Java.Lang.Thread/IUncaughtExceptionHandlerInvoker.UncaughtException (Java.Lang.Thread,Java.Lang.Throwable) <0x0013b>
> [MonoDroid] at Android.Runtime.UncaughtExceptionHandler.UncaughtException (Java.Lang.Thread,Java.Lang.Throwable) <0x0014f>
> [MonoDroid] at Java.Lang.Thread/IUncaughtExceptionHandlerInvoker.n_UncaughtException_Ljava_lang_Thread_Ljava_lang_Throwable_ (intptr,intptr,intptr,intptr) <0x0007f>
> [MonoDroid] at (wrapper dynamic-method) object.8c5251c8-50a8-4596-a416-824c8e88dc09 (intptr,intptr,intptr,intptr) <0x0004b>

That actually looks like an internal bug. In order to raise the AppDomain.UnhandledException event, I create a java.lang.Thread.UncaughtExceptionHandler instance and pass it to Thread.setDefaultUncaughtExceptionHandler(). My internal handler eventually does:

	if (defaultHandler != null)
		defaultHandler.UncaughtException (thread, ex);

It's the second line which is throwing, presumably because `defaultHandler.Handle` is IntPtr.Zero (hoisted by my own petard?)...which makes no sense at all, as I can't imagine any way that defaultHandler would be invalidated...
Comment 7 David 2015-08-27 19:26:47 UTC
Ok, I think you are right about fragments being "weird". I followed your hunch that they get garbage collected and the reference of fragment in the closure becomes null. I simply kept the fragments in their parent activity and this seems to have solved the problem. Thanks so much for your help, I appreciate that!

I guess that is still a bug or at least should the Xamarin samples be updated that show you that you can use the fragment in the closure like that.