[Android] Material 3 - TrySetAppBarLiftTargetIfOnScreen might throw java.lang.NullPointerException
Description
Microsoft.Maui.Platform.AppbarLayoutExtensions.TrySetAppBarLiftTargetIfOnScreen
(added in #35181, "[Android] Fix AppBar flicker on CheckBox/Switch toggle with
Material 3") crashes the app with a java.lang.NullPointerException when the
Runnable it schedules via MauiScrollView.PostTrySetAppBarLiftTargetIfOnScreen
fires while the app is under GC pressure and the scroll view hierarchy is
churning (CarouselView page swipes, CollectionView/ItemsSource resets, rapid
navigation).
The crash is:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Rect.set(int, int, int, int)' on a null object reference
at android.view.View.getGlobalVisibleRect(View.java:...)
at android.view.View.getGlobalVisibleRect(View.java:...)
at mono.java.lang.Runnable.n_run(Native Method)
at mono.java.lang.Runnable.run(Runnable.java:29)
...with the managed side pointing exactly at:
Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod(...)
Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeNonvirtualBooleanMethod(...)
Android.Views.View.GetGlobalVisibleRect(Rect r)
Microsoft.Maui.Platform.AppbarLayoutExtensions.TrySetAppBarLiftTargetIfOnScreen(View view)
Microsoft.Maui.Platform.MauiScrollView.<GetOrCreateSetAppBarLiftTargetRunnable>b__39_0()
Java.Lang.Runnable.Run()
Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)This matches our original field report byte-for-byte, including the
IRunnableInvoker.n_Run frame naming. Building the same repro with
PDB-symbolicated line numbers against current main source resolves that
TrySetAppBarLiftTargetIfOnScreen frame to AppbarLayoutExtensions.cs:56:
if (view.GetGlobalVisibleRect(state.VisibleRect))state.VisibleRect is a readonly Rect field allocated once per view (cached
in a ConditionalWeakTable<View, AppBarLiftState>) and reused across calls.
The NPE's own message — Rect.set(...) on a null object reference — means the
argument passed to getGlobalVisibleRect resolved to a null JNI reference
on the Java side, even though state.VisibleRect is a live, non-null managed
object and is never explicitly disposed anywhere in this code path. This
points at the cached Rect's Java peer being invalidated/collected
out from under the managed reference (a Java.Interop GC-bridge race), rather
than a plain "view already detached" case — the method already guards against
that with view.IsAlive() && view.IsAttachedToWindow && view.Visibility == Visible
before ever touching the Rect, and those guards are passing right up until
the crash.
This is fully reproducible (not a one-off field report) — see attached repro project and logs below.
Steps to Reproduce
- Create/open a .NET MAUI Android app with
<UseMaterial3>true</UseMaterial3>set in the csproj (this is what turns onRuntimeFeature.IsMaterial3Enabled, which gates the code path). - Host a
ScrollView(directly, or viaCarouselView/CollectionViewitem templates) under a Shell or NavigationPage AppBar. - Churn the scroll view hierarchy quickly — e.g. repeatedly jump a
CarouselView.Positionto random indices and periodically replace itsItemsSourcewholesale — while the app is under GC pressure.
A minimal repro project that does exactly this is attached
(AppBarLiftCrashRepro, see the accompanying zip / repo folder). It starts
its stress loop automatically as soon as the page appears — no manual
interaction needed. On both devices we tested, it crashes within 5-30 seconds:
sdk_gphone64_arm64emulator, Android 16 (API 36)- Pixel 9a, Android 17 (API 37)
Full logcat crash excerpts for both are included as crash-logs/*.log in the
repro project.
Expected Behavior
The AppBar lift-on-scroll target update is a cosmetic, best-effort UI enhancement (elevation/shadow on the toolbar) and should never be able to crash the app, regardless of how quickly the scroll view hierarchy is churning or GC timing lines up.
Actual Behavior
App crashes with the NullPointerException above.
Basic Information
- .NET MAUI version:
Microsoft.Maui.Controls 10.0.101— matches exactly what our production app is pinned to. - Also confirmed present (via source inspection) on the current
mainbranch, resolving toMicrosoft.Maui.Controls 11.0.0-rc.1.26451.6— the fileAppbarLayoutExtensions.cswas added whole-cloth by #35181 and has had no further commits since, so the same unguardedRectreuse exists there too. - Affected platform: Android only (the feature is Android-specific)
- IDE: N/A — reproduced via
dotnet build -t:Run
Environment
Emulator:
Device: sdk_gphone64_arm64 (emulator-5554)
Android: 16 (API 36)
ABI: arm64-v8aPhysical device:
Device: Pixel 9a
Android: 17 (API 37)Repro project TargetFramework: net10.0-android
MauiVersion: 10.0.101
SDK used: 10.0.300 (maui-android workload 10.0.20)
Feature switch under test: <UseMaterial3>true</UseMaterial3>
Link to public reproduction project repository
https://github.com/EmilienDup/MAUIAndroidAppBarLiftTargetNullPointerException
Version with bug
10.0.101
Is this a regression from previous behavior?
No, this is something new
Last version that worked well
No response
Affected platforms
Android
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
Emulator (`crash-logs/emulator-android16-api36.log`):
09-17 11:29:04.375 23785 23785 E AndroidRuntime: FATAL EXCEPTION: main
09-17 11:29:04.375 23785 23785 E AndroidRuntime: Process: com.companyname.appbarliftcrashrepro, PID: 23785
09-17 11:29:04.375 23785 23785 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Rect.set(int, int, int, int)' on a null object reference
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.view.View.getGlobalVisibleRect(View.java:20764)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.view.View.getGlobalVisibleRect(View.java:20788)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at mono.java.lang.Runnable.n_run(Native Method)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at mono.java.lang.Runnable.run(Runnable.java:29)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:1070)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:125)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.os.Looper.dispatchMessage(Looper.java:333)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:263)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.os.Looper.loop(Looper.java:367)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:9282)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
09-17 11:29:04.375 23785 23785 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
09-17 11:29:04.378 23785 23785 I MonoDroid: Java.Lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Rect.set(int, int, int, int)' on a null object reference
09-17 11:29:04.378 23785 23785 I MonoDroid: at Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod(JniObjectReference instance, JniObjectReference type, JniMethodInfo method, JniArgumentValue* args)
09-17 11:29:04.378 23785 23785 I MonoDroid: at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeNonvirtualBooleanMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters)
09-17 11:29:04.378 23785 23785 I MonoDroid: at Android.Views.View.GetGlobalVisibleRect(Rect r)
09-17 11:29:04.378 23785 23785 I MonoDroid: at Microsoft.Maui.Platform.AppbarLayoutExtensions.TrySetAppBarLiftTargetIfOnScreen(View view)
09-17 11:29:04.378 23785 23785 I MonoDroid: at Microsoft.Maui.Platform.MauiScrollView.<GetOrCreateSetAppBarLiftTargetRunnable>b__39_0()
09-17 11:29:04.378 23785 23785 I MonoDroid: at Java.Lang.Runnable.Run()
09-17 11:29:04.378 23785 23785 I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)
09-17 11:29:04.378 23785 23785 I MonoDroid: --- End of managed Java.Lang.NullPointerException stack trace ---
Physical Pixel 9a (`crash-logs/pixel9a-android17-api37.log`):
09-17 11:30:26.515 22887 22887 E AndroidRuntime: FATAL EXCEPTION: main
09-17 11:30:26.515 22887 22887 E AndroidRuntime: Process: com.companyname.appbarliftcrashrepro, PID: 22887
09-17 11:30:26.515 22887 22887 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Rect.set(int, int, int, int)' on a null object reference
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.view.View.getGlobalVisibleRect(View.java:20946)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.view.View.getGlobalVisibleRect(View.java:20970)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at mono.java.lang.Runnable.n_run(Native Method)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at mono.java.lang.Runnable.run(Runnable.java:29)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:1095)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.os.Handler.dispatchMessageImpl(Handler.java:135)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:125)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:296)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.os.Looper.loop(Looper.java:397)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:9523)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:575)
09-17 11:30:26.515 22887 22887 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:939)
09-17 11:30:26.535 22887 22887 I MonoDroid: Java.Lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Rect.set(int, int, int, int)' on a null object reference
09-17 11:30:26.535 22887 22887 I MonoDroid: at Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod(JniObjectReference instance, JniObjectReference type, JniMethodInfo method, JniArgumentValue* args)
09-17 11:30:26.535 22887 22887 I MonoDroid: at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeNonvirtualBooleanMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters)
09-17 11:30:26.535 22887 22887 I MonoDroid: at Android.Views.View.GetGlobalVisibleRect(Rect r)
09-17 11:30:26.535 22887 22887 I MonoDroid: at Microsoft.Maui.Platform.AppbarLayoutExtensions.TrySetAppBarLiftTargetIfOnScreen(View view)
09-17 11:30:26.535 22887 22887 I MonoDroid: at Microsoft.Maui.Platform.MauiScrollView.<GetOrCreateSetAppBarLiftTargetRunnable>b__39_0()
09-17 11:30:26.535 22887 22887 I MonoDroid: at Java.Lang.Runnable.Run()
09-17 11:30:26.535 22887 22887 I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)
09-17 11:30:26.535 22887 22887 I MonoDroid: --- End of managed Java.Lang.NullPointerException stack trace ---Source: dotnet/maui