Skip to content

Commit e202222

Browse files
committed
Fix release mode crash for android
1 parent 113fe9c commit e202222

File tree

1 file changed

+90
-49
lines changed

1 file changed

+90
-49
lines changed

src/Bible.Alarm/Platforms/Android/MainActivity.cs

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,80 +11,121 @@
1111
using Java.Lang;
1212
using Serilog;
1313
using Exception = System.Exception;
14+
using AndroidLog = Android.Util.Log;
1415

1516
namespace Bible.Alarm.Platforms.Android;
1617

1718
[Activity(Label = "Bible Alarm", Theme = "@style/MainTheme", LaunchMode = LaunchMode.SingleTop, MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
1819
public class MainActivity : MauiAppCompatActivity
1920
{
20-
private static readonly ILogger logger = Log.ForContext<MainActivity>();
21+
// Lazy logger initialization to ensure Serilog is configured first
22+
private static ILogger? logger;
23+
private static ILogger Logger => logger ??= Serilog.Log.ForContext<MainActivity>();
24+
2125
private IAndroidAlarmHandler? alarmHandler;
2226
private DateTime? lastResumeTime;
2327

2428
protected override void OnCreate(Bundle? savedInstanceState)
2529
{
26-
// IMPORTANT: Create MAUI app BEFORE calling base.OnCreate()
27-
// This ensures the service provider is available when MAUI's lifecycle events try to access it
28-
// If the app was previously disposed (swiped out), CreateAndStore() will create a new app instance
29-
MauiAppHolder.CreateAndStore();
30+
// Set up global exception handlers FIRST, before any other operations
31+
// This ensures we catch exceptions even if they occur during initialization
32+
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
33+
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
3034

31-
// Fix for MAUI NavigationRootManager fragment state restoration issue
32-
// If savedInstanceState contains stale fragment state that references non-existent views (like id/legacy),
33-
// pass null to prevent fragment restoration. This can happen after app updates or when MAUI framework
34-
// tries to restore fragments with view IDs that no longer exist.
35-
Bundle? safeSavedInstanceState = savedInstanceState;
36-
if (savedInstanceState != null)
35+
// Log early using Android log in case Serilog isn't initialized yet
36+
AndroidLog.Info("MainActivity", "OnCreate started");
37+
38+
try
3739
{
38-
try
40+
// IMPORTANT: Create MAUI app BEFORE calling base.OnCreate()
41+
// This ensures the service provider is available when MAUI's lifecycle events try to access it
42+
// If the app was previously disposed (swiped out), CreateAndStore() will create a new app instance
43+
AndroidLog.Info("MainActivity", "Calling MauiAppHolder.CreateAndStore()");
44+
MauiAppHolder.CreateAndStore();
45+
AndroidLog.Info("MainActivity", "MauiAppHolder.CreateAndStore() completed");
46+
47+
// Fix for MAUI NavigationRootManager fragment state restoration issue
48+
// If savedInstanceState contains stale fragment state that references non-existent views (like id/legacy),
49+
// pass null to prevent fragment restoration. This can happen after app updates or when MAUI framework
50+
// tries to restore fragments with view IDs that no longer exist.
51+
Bundle? safeSavedInstanceState = savedInstanceState;
52+
if (savedInstanceState != null)
3953
{
40-
// Check if savedInstanceState contains fragment state that might be stale
41-
var hasFragmentState = false;
42-
foreach (var key in savedInstanceState?.KeySet() ?? [])
54+
try
4355
{
44-
if (key != null && (key.Contains("fragment") || key.Contains("Fragment") ||
45-
key.Contains("androidx.lifecycle") || key.Contains("android:support")))
56+
// Check if savedInstanceState contains fragment state that might be stale
57+
var hasFragmentState = false;
58+
foreach (var key in savedInstanceState?.KeySet() ?? [])
4659
{
47-
hasFragmentState = true;
48-
break;
60+
if (key != null && (key.Contains("fragment") || key.Contains("Fragment") ||
61+
key.Contains("androidx.lifecycle") || key.Contains("android:support")))
62+
{
63+
hasFragmentState = true;
64+
break;
65+
}
4966
}
50-
}
5167

52-
if (hasFragmentState)
68+
if (hasFragmentState)
69+
{
70+
Logger.Information("Detected fragment state in savedInstanceState - ignoring to prevent NavigationRootManager crash");
71+
// Pass null to prevent fragment restoration - MAUI will recreate navigation from scratch
72+
safeSavedInstanceState = null;
73+
}
74+
}
75+
catch (Exception ex)
5376
{
54-
logger.Information("Detected fragment state in savedInstanceState - ignoring to prevent NavigationRootManager crash");
55-
// Pass null to prevent fragment restoration - MAUI will recreate navigation from scratch
77+
Logger.Warning(ex, "Error checking fragment state - ignoring savedInstanceState to be safe");
5678
safeSavedInstanceState = null;
5779
}
5880
}
59-
catch (Exception ex)
60-
{
61-
logger.Warning(ex, "Error checking fragment state - ignoring savedInstanceState to be safe");
62-
safeSavedInstanceState = null;
63-
}
64-
}
6581

66-
base.OnCreate(safeSavedInstanceState);
82+
AndroidLog.Info("MainActivity", "Calling base.OnCreate()");
83+
base.OnCreate(safeSavedInstanceState);
84+
AndroidLog.Info("MainActivity", "base.OnCreate() completed");
6785

68-
// Set up global exception handlers
69-
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
70-
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
86+
// NOTE: Do NOT call InitializePlatformBootstrap here for foreground launches
87+
// WindowSetupService.CreateWindow() will handle bootstrap and send InitializedMessage
88+
// Bootstrap is only needed here for background services/jobs, not for foreground UI launches
89+
90+
AndroidLog.Info("MainActivity", "Setting up intents and background tasks");
91+
// Handle incoming intents (e.g., from notifications)
92+
HandleIncomingIntent();
7193

72-
// NOTE: Do NOT call InitializePlatformBootstrap here for foreground launches
73-
// WindowSetupService.CreateWindow() will handle bootstrap and send InitializedMessage
74-
// Bootstrap is only needed here for background services/jobs, not for foreground UI launches
94+
// Set up background tasks
95+
SetupBackgroundTasks();
96+
97+
AndroidLog.Info("MainActivity", "OnCreate completed successfully");
98+
}
99+
catch (Exception ex)
100+
{
101+
// Log the exception using Android log first (most reliable)
102+
AndroidLog.Error("MainActivity", $"FATAL ERROR in OnCreate: {ex.GetType().Name}: {ex.Message}");
103+
AndroidLog.Error("MainActivity", $"Stack trace: {ex.StackTrace}");
104+
if (ex.InnerException != null)
105+
{
106+
AndroidLog.Error("MainActivity", $"Inner exception: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
107+
}
75108

76-
// Handle incoming intents (e.g., from notifications)
77-
HandleIncomingIntent();
109+
// Also try to log with Serilog if available
110+
try
111+
{
112+
Logger.Fatal(ex, "Fatal error in MainActivity.OnCreate - app will crash");
113+
}
114+
catch
115+
{
116+
// Serilog not available, already logged with AndroidLog
117+
}
78118

79-
// Set up background tasks
80-
SetupBackgroundTasks();
119+
// Re-throw to let Android handle the crash (we can't recover from OnCreate failures)
120+
throw;
121+
}
81122
}
82123

83-
private void UnobservedTaskExceptionHandler(object? sender, UnobservedTaskExceptionEventArgs e) => logger.Error(e.Exception, "Unobserved task exception.");
124+
private void UnobservedTaskExceptionHandler(object? sender, UnobservedTaskExceptionEventArgs e) => Logger.Error(e.Exception, "Unobserved task exception.");
84125

85126
private void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
86127
{
87-
logger.Error(e.ExceptionObject as Exception, "Unhandled exception. IsTerminating: {IsTerminating}",
128+
Logger.Error(e.ExceptionObject as Exception, "Unhandled exception. IsTerminating: {IsTerminating}",
88129
e.IsTerminating);
89130
}
90131

@@ -116,7 +157,7 @@ private void HandleIncomingIntent()
116157
}
117158
catch (Exception e)
118159
{
119-
logger.Error(e, "Error handling incoming alarm intent");
160+
Logger.Error(e, "Error handling incoming alarm intent");
120161
}
121162
});
122163
}
@@ -137,7 +178,7 @@ private void SetupBackgroundTasks()
137178
}
138179
catch (Exception ex)
139180
{
140-
logger.Error(ex, "Error setting up background tasks");
181+
Logger.Error(ex, "Error setting up background tasks");
141182
}
142183
});
143184
}
@@ -154,7 +195,7 @@ protected override void OnStart()
154195
catch (IllegalArgumentException ex) when (ex.Message?.Contains("No view found for id") == true && ex.Message?.Contains("legacy") == true)
155196
{
156197
// Fragment restoration failed due to stale state - clear fragments and retry
157-
logger.Warning(ex, "Fragment restoration failed due to stale state - clearing fragments and retrying");
198+
Logger.Warning(ex, "Fragment restoration failed due to stale state - clearing fragments and retrying");
158199
try
159200
{
160201
var fragmentManager = SupportFragmentManager;
@@ -182,7 +223,7 @@ protected override void OnStart()
182223
}
183224
catch (Exception retryEx)
184225
{
185-
logger.Error(retryEx, "Failed to recover from fragment restoration error - app may be in inconsistent state");
226+
Logger.Error(retryEx, "Failed to recover from fragment restoration error - app may be in inconsistent state");
186227
// Re-throw to let Android handle it
187228
throw;
188229
}
@@ -236,13 +277,13 @@ protected override void OnSaveInstanceState(Bundle outState)
236277
outState.Remove(key);
237278
}
238279

239-
logger.Information("Prevented saving {Count} fragment state keys to avoid NavigationRootManager crash", keysToRemove.Count);
280+
Logger.Information("Prevented saving {Count} fragment state keys to avoid NavigationRootManager crash", keysToRemove.Count);
240281
}
241282
}
242283
}
243284
catch (Exception ex)
244285
{
245-
logger.Warning(ex, "Error in OnSaveInstanceState - proceeding anyway");
286+
Logger.Warning(ex, "Error in OnSaveInstanceState - proceeding anyway");
246287
// Still call base to ensure other state is saved
247288
try
248289
{
@@ -277,7 +318,7 @@ protected override void OnDestroy()
277318
}
278319
catch (Exception ex)
279320
{
280-
logger.Error(ex, "Error in MainActivity.OnDestroy while attempting to dismiss player");
321+
Logger.Error(ex, "Error in MainActivity.OnDestroy while attempting to dismiss player");
281322
}
282323

283324

0 commit comments

Comments
 (0)