Resuming app from background service results in System.InvalidOperationException MauiContext is null.
Description
Detected System.InvalidOperationException MauiContext is null via sentry.io when the app was resumed by the user approx 10 minutes of background services.
It's assumed the user either resumed the app, or clicked not the foreground service notification to resume the app.
The call stack is all deep within MAUI so I'm not sure how to provide more details that will help diagnose this?
Steps to Reproduce
Published my app to Google Play for internal testing then started seeing these exceptions, it's unclear what the user was doing, but logs indicates the following:
- User launches app
- The app captures GPS updates using an Android background service
- About 10 minutes elapses since the users last UI interaction, the app is assumed to be in the background during this time
- The user resumes the app which displays our PleaseWaitPage page
- The app is automatically navigated to a dashboard page which includes a tabbed page with three tabs
- The 'System.InvalidOperationException: MauiContext is null' is then thrown causing an unhandled exception, it's unclear whether this is the dashboard page or tabbed page as the call stack is all MAUI centric / low-level.
Note that Sentry shows this is .NET 9.0.0-rc.2.24473.5 whereas the drop-down in this ticket only let's me choose 9.0.0-rc.2.24503.2 as the closest match.
The Android activity is set to be LaunchMode = LaunchMode.SingleTask
Link to public reproduction project repository
No response
Version with bug
9.0.0-rc.2.24503.2
Is this a regression from previous behavior?
Yes, this used to work in Xamarin.Forms
Last version that worked well
Unknown/Other
Affected platforms
Android
Affected platform versions
Android 13 / CMA-LX1 + Android 14 / Pixel 8
Did you find any workaround?
Yes - within the CreateWindow method I now return the existing Window if one has previously been created, e.g.
Window originalWindow = null;
protected override Window CreateWindow(IActivationState activationState)
{
if (originalWindow != null) {
return originalWindow;
}
originalWindow = new Window(PageManager.GetPleaseWaitPage());
return originalWindow;
}
Relevant log output
No response
@RobbiewOnline can you post any code with how you handle Window and setting the mainpage on your application?
Are you reusing the same window across activity recreates?
Reusing the same page?
Nm, I repro'd
Actually @RobbiewOnline
Can you post the code of how you setup the main page you are setting on the app/window?
I'm curious to see your scenario. This can happen if you reuse the same page on different window instances.
Possible fix here https://github.com/dotnet/maui/tree/fix_25443
Hi,
I wasn't doing anything 'special' in my code, I was following the standard code sample that does the following:
protected override Window CreateWindow(IActivationState activationState)
{
return new Window(PageManager.GetPleaseWaitPage());
}
But it would seem when the application is being returned to from a background service, which uses an intent to launch / return to the app, the CreateWindow is called again, which promptly creates a new Window.
Somewhere in this lifecycle I get behavioural issues, like the error previously reported.
I modified the code this evening to keep a reference to the original Window created and if a reference remains then re-use it.
This now launches from the persistent foreground service notification 🕺
So app.xaml.cs window registration becomes this...
Window originalWindow = null;
protected override Window CreateWindow(IActivationState activationState)
{
if (originalWindow != null) {
Logger.LogWithoutStorage(this, $"App CreateWindow invoked - reusing originalWindow");
return originalWindow;
}
originalWindow = new Window(PageManager.GetPleaseWaitPage()); // Note can't call base.CreateWindow(activationState);
return originalWindow;
}
I don't know if this is a hack/work-around, or what I should have been doing in the first place?
The MainActivity is defined like this, most of it's logging because I was trying to track down the crashes / ANRs.
[Activity(
// Label = "MyTeamSafe"
// Icon = "@mipmap/icon"
//Theme = "@style/MainTheme",
Theme = "@style/Maui.SplashTheme",
MainLauncher = true,
Exported = true,
LaunchMode = LaunchMode.SingleTask, // Required for Media playback
ScreenOrientation = ScreenOrientation.Portrait,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
public MainActivity()
{
Logger.LogWithoutStorage(this, $"MainActivity() Android SDK: {Build.VERSION.SdkInt}, Codename: {Build.VERSION.Codename} stacktrace:{new StackTrace().ToString()}");
}
protected override void OnCreate(Bundle bundle)
{
try
{
Logger.LogWithoutStorage(this, $"OnCreate invoked stacktrace:{new StackTrace().ToString()}");
Logger.LogWithoutStorage(this, $"OnCreate IgnoreDeleteNotifications=false");
PlatformDifferences.IgnoreDeleteNotifications = false; // Only clears if launching interactively (not a notification)
base.OnCreate(bundle);
Platform.Init(this, bundle);
CrossFingerprint.SetCurrentActivityResolver(() => Platform.CurrentActivity);
Logger.LogWithoutStorage(this, "OnCreate passing intent to Shove");
try
{
((ShovePlugin)CrossShovePlugin.Current)?.OnNewIntent(Platform.CurrentActivity, Intent);
}
catch (Exception e)
{
CrashHelper.HandleException(false, "Couldn't delegate intent to Shove", this, e);
}
}
catch (Exception ex)
{
Logger.LogWithoutStorage(this, $"OnCreate exception caught reason: {ex.Message}");
}
Logger.LogWithoutStorage(this, "OnCreate - done");
}
For context the persistent notification is setup like this (it's a requirement of Android if you want to promote a GPS service to the foreground) and clicking on the notification (after swiping away the app) would result in a new window being created even though the app hasn't been terminated (because it's running as a foreground service), at least that's my interpretation of it ...
private void registerForPersistentNotificationService()
{
if (!NeedsToRegisterForPersitentNotification)
{
Logger.Log(this, $"registerForService started NeedsToRegisterForPersitentNotification:" + NeedsToRegisterForPersitentNotification + " - ignored (already done)");
}
else
{
Logger.Log(this, $"registerForService started NeedsToRegisterForPersitentNotification:" + NeedsToRegisterForPersitentNotification);
NeedsToRegisterForPersitentNotification = false;
if (Build.VERSION.SdkInt >= BuildVersionCodes.O) // Oreo = Android SDK 26 / Android 8.0+
{
#pragma warning disable CA1416 // Validate platform compatibility
Logger.Log(this, $"registerForService Oreo+ is creating channel");
var CHANNEL_ID = "MyApp-BackgroundService";
var channel = new NotificationChannel(CHANNEL_ID, "Channel", NotificationImportance.Default)
{
Description = "Foreground Service Channel"
};
Logger.Log(this, $"registerForService creating notification channel");
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
Logger.Log(this, $"registerForService creating notification");
var nb = new Notification.Builder(this, CHANNEL_ID);
setCommonNotification(nb);
var notification = nb.Build();
// Enlist this instance of the service as a foreground service
Logger.Log(this, $"registerForService starting foreground service");
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
#pragma warning restore CA1416 // Validate platform compatibility
}
else if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) // Lollipop = Android SDK 21 / Android 5.0+
{
// Lollipop and onwards
Logger.Log(this, $"registerForService Lollipop+ - creating notification");
var nb = new Notification.Builder(this);
setCommonNotification(nb);
var notification = nb
.Build();
// Enlist this instance of the service as a foreground service
Logger.Log(this, $"registerForService starting service");
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
} else {
Logger.Log(this, $"registerForService Pre-Lollipop - not supported");
}
}
Logger.Log(this, $"registerForService completed");
}
private void setCommonNotification(Notification.Builder nb)
{
var activity = new Intent(MauiApplication.Current.ApplicationContext, typeof(MainActivity));
PendingIntent pendingIntent = null;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.S)
{
pendingIntent = PendingIntent.GetActivity(this, 0, activity, PendingIntentFlags.Immutable);
}
else
{
pendingIntent = PendingIntent.GetActivity(this, 0, activity, 0);
}
nb.SetContentTitle("MyApp");
nb.SetContentText(Text);
nb.SetOngoing(true);
nb.SetContentIntent(pendingIntent);
if (Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O)
{
nb.SetSmallIcon(Resource.Drawable.icon_as_resource);
} else
{
nb.SetSmallIcon(Resource.Drawable.ic_notification_icon_as_resource);
nb.SetColor(Resource.Color.colorAccent); // Resource.Drawable.icon
}
}
Your thoughts are very much appreciated as I'd like to understand whether this is a MAUI lifecycle bug (resuming an app from a persistent foreground notification), or my missunderstanding of creating a Window and assuming when CreateWindow is called it should be created each invocation.
Yea, I think this is an area where we could throw a better exception or make the code run in a little bit more helpful of a fashion. Like, perhaps we should just auto remove a page from a previous window...
I've moved this into our servicing milestone so we can give it a little more thought about best bath here
I think your workaround is "valid"
My suggestion was going to either be
- what you did
- on the 'originalWindow' you could set the
Pageto null and I think that'll work as well
so like this might even work
var waitPage = PageManager.GetPleaseWaitPage();
if (waitPage.Window is not null)
waitPage.Window.Page = null;
return new Window(waitPage);
Thanks @PureWeen
From my perspsective if the original Page(s) and Window objects are all still referencable, then there's no point creating a new Window and trying to reassociate the pages to the new Window?
It feels like in some Android specific lifecycles (e.g. resuming the app from a background/foreground service system tray banner) then the CreateWindow call should be avoided as everything is still working as-is, no new Window needs to be created.
I'm going to tag this issue in with #25274 as these might be related.
Thanks @PureWeen
From my perspsective if the original Page(s) and Window objects are all still referencable, then there's no point creating a new Window and trying to reassociate the pages to the new Window?
It feels like in some Android specific lifecycles (e.g. resuming the app from a background/foreground service system tray banner) then the CreateWindow call should be avoided as everything is still working as-is, no new Window needs to be created.
I'm going to tag this issue in with #25274 as these might be related.
Possibly, the problem is, is that we have limited context as to why CreateWindow is being called. These are all just tied to platform life cycle events.
Maybe the user wants a new activity to handle a new intent? maybe they want to handle notifications in a new intent/window?
We could add some type registration hooks, or a way for users to specify that an app is a single page app and to always reuse the window/page as singletons.
I don't really want to make assumptions for the user why CreateWindow is being called but we could probably add some ways for users to express that intent so it's not as leaky.
For example, in your scenario you are already using a singleton Page so why aren't you extending that concept to your window?
Ran into the same issue: https://github.com/dotnet/maui/issues/28906#issuecomment-2808878873. In our case the app uses SSO so it backgrounds and foregrounds. The app doesnt actually crash but the error is logged via sentry 🤷🏽
The workaround mentioned in here is not working.
As i mentioned in #28906 : We set the launchMode to SingleInstance, because we do not want to have multiple instances of the app. If we are trying to tap on a .trs file in the file manager of the device and select the app, then the import will work. But we have two instances of the app. The app itself and another instance inside the file manager app. If we go back to the app itself the app crashes with another exception:
System.InvalidOperationException: 'This window is already associated with an active Activity (Comprion.TraceCase.App.Android.MainActivity). Please override CreateWindow on Comprion.TraceCase.Views.App to add support for multiple activities https://aka.ms/maui-docs-create-window or set the LaunchMode to SingleTop on Comprion.TraceCase.App.Android.MainActivity.'
I write a comment to this bug because my bug was closed. It would be great to get an answer because we are not able to publish our app to the stores regarding this problem.
Please see repro: https://github.com/Syed-RI/Crash-with-CreateWindow including video.
- Pull and run the project
- Press the back navigation button. Resume the app from the task manager
Please see repro: https://github.com/Syed-RI/Crash-with-CreateWindow including video.
- Pull and run the project
- Press the back navigation button. Resume the app from the task manager
The workaround mentioned in here is not working.
As i mentioned in #28906 : We set the launchMode to SingleInstance, because we do not want to have multiple instances of the app. If we are trying to tap on a .trs file in the file manager of the device and select the app, then the import will work. But we have two instances of the app. The app itself and another instance inside the file manager app. If we go back to the app itself the app crashes with another exception:
System.InvalidOperationException: 'This window is already associated with an active Activity (Comprion.TraceCase.App.Android.MainActivity). Please override CreateWindow on Comprion.TraceCase.Views.App to add support for multiple activities https://aka.ms/maui-docs-create-window or set the LaunchMode to SingleTop on Comprion.TraceCase.App.Android.MainActivity.'I write a comment to this bug because my bug was closed. It would be great to get an answer because we are not able to publish our app to the stores regarding this problem.
Can you open a new bug with a repro so we can properly track?
@PureWeen Is there a date when a fix will go in?
We have published a new version of our App. Now we see this issue in our telemetry data collection as well.
- Previous version used MAUI 8.0.93. This issue did not occur.
- Current published version uses MAUI 9.0.100. Here we see that some users have this issue.
- Android 15, Android 16
- We are using LaunchMode.SingleInstance
Error and stack trace
"System.InvalidOperationException","MauiContext is null."
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window.get_MauiContext"",""level"":0,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Platform.AlertManager.Subscribe"",""level"":1,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Platform.AlertManager.Subscribe"",""level"":2,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window.OnPageHandlerChanged"",""level"":3,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window.OnPageChanged"",""level"":4,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window+<>c.<.cctor>b__224_0"",""level"":5,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.BindableObject.OnBindablePropertySet"",""level"":6,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Element.OnBindablePropertySet"",""level"":7,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.BindableObject.SetValueActual"",""level"":8,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.BindableObject.SetValueCore"",""level"":9,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.BindableObject.SetValue"",""level"":10,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window.set_Page"",""level"":11,""line"":0},
{""assembly"":""Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",""method"":""Microsoft.Maui.Controls.Window..ctor"",""level"":12,""line"":0},
{""assembly"":""OurApp, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null"",""method"":""OurApp.App.CreateWindow"",""level"":13,""line"":0},
@omghb I'm unsure how you did the conversion to the "new" method, but we created new properties for Page and Shell in app.xaml.cs. We tried several changes to Launchmode, but none of them helped. Since we were getting so many crashes and getting hit on ratings, I went back to the "classic" method and had the new properties wrap back to Current.MainPage and also commented out the CreateWindow.
After that, all of the MauiContext is null crashes went away on .net 8.
Just upgraded to .net9, and now just got it again :(
@PureWeen Is there a date when a fix will go in for this? A partial fix would certainly be better than none.
Are there any updates? A terribly annoying bug that has been around for a long time. When will it finally be fixed? Among other things, it blocks the update to .net 10.
I'm still facing this issue. Is there any update when it will be fixed?