I'm trying to implement Firebase Messaging (by Plugin.Firebase nuget) for an NET MAUI Blazor app.
I've managed to send and receive notifications from FCM console, but when my app is minimized, the notification tap restores it but with purple background only (default in MAUI app).
This only happens when there is a BlazorWebView in MainPage.xml, I tried the same with only a label above it, and the label appears fine when there's no BlazorWebView.
However, it seems no breakpoints in my Razor files get hit, so I don't know what could be the culprit, the only code that gets executed on notification tap is MainAcitivity.OnCreate which calls base.OnCreate which executes MauiProgram.RegisterFirebaseServices and crashes.
I've stripped down all my Razor pages to bare minimum, but no breakpoints are hit, so even though the only way the app crashes is with BlazorWebView, I'm not sure where to look. The bones for NET MAUI Firebase implementation were taken from: https://github.com/coop-tim/maui-sample
MainActivity.cs:
using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Microsoft.Extensions.Configuration;
using Plugin.Firebase.CloudMessaging;
namespace MauiSample;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
public static bool AppCenterConfigured { get; set; }
protected override void OnCreate(Bundle? savedInstanceState)
{
try
{
var intent = this.Intent;
HandleIntent(Intent);
CreateNotificationChannelIfNeeded();
base.OnCreate(savedInstanceState);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
protected override void OnNewIntent(Intent? intent)
{
try
{
base.OnNewIntent(intent);
HandleIntent(intent);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void HandleIntent(Intent? intent)
{
try
{
if (intent is not null)
{
FirebaseCloudMessagingImplementation.OnNewIntent(intent);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
try
{
Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
#if ANDROID23_0_OR_GREATER
#pragma warning disable CA1416 // Validate platform compatibility
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
#pragma warning restore CA1416 // Validate platform compatibility
#endif
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void CreateNotificationChannelIfNeeded()
{
try
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.PostNotifications) != Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.PostNotifications }, 0);
}
}
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
CreateNotificationChannel();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void CreateNotificationChannel()
{
try
{
var channelId = $"{PackageName}.general";
#if ANDROID26_0_OR_GREATER
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
if (notificationManager is not null)
{
if ((int)Build.VERSION.SdkInt > 26)
{
#pragma warning disable CA1416 // Validate platform compatibility
var channel = new NotificationChannel(channelId, "General", NotificationImportance.Default);
notificationManager.CreateNotificationChannel(channel);
#pragma warning restore CA1416 // Validate platform compatibility
}
}
FirebaseCloudMessagingImplementation.ChannelId = channelId;
#endif
//FirebaseCloudMessagingImplementation.SmallIconRef = Resource.Drawable.ic_push_small;
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
MauiProgram.cs:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using WaybackMaui.Authentication;
using Radzen;
using Microsoft.Kiota.Abstractions.Authentication;
using Shared.Settings;
using Microsoft.Extensions.Configuration;
using System.Reflection;
using Microsoft.Maui.LifecycleEvents;
using Plugin.Firebase.CloudMessaging;
using Plugin.Firebase.Analytics;
using Wayback.ViewModels;
using Serilog;
using Plugin.Firebase.CloudMessaging.EventArgs;
using WaybackMaui.Services;
#if IOS
using Plugin.Firebase.Core.Platforms.iOS;
#elif ANDROID
using Plugin.Firebase.Core.Platforms.Android;
#endif
namespace WaybackMaui
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // Set the minimum log level
.WriteTo.Console() // Add console sink
.WriteTo.AndroidLog()
.CreateLogger();
Log.Logger.Debug("proba konzole");
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.RegisterFirebaseServices()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Use Serilog for logging
builder.Logging.ClearProviders(); // Clear default logging providers
builder.Logging.AddSerilog(); // Use Serilog
builder.Services.AddAuthorizationCore();
builder.Services.AddTransient<CustomAuthenticationStateProvider>();
builder.Services.AddTransient<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());
builder.Services.AddTransient<DialogService>();
builder.Services.AddTransient<IAccessTokenProvider, AccessTokenProvider>();
builder.Services.AddTransient<JwtTokenAuthenticationProvider>();
builder.Services.AddTransient<IJwtTokenAuthenticationProvider, JwtTokenAuthenticationProvider>();
builder.Services.AddTransient<ICustomAuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddTransient<IFirebaseService, FirebaseService>();
builder.Services.AddTransient<FirebaseService>();
//builder.Services.AddSingleton<IFirebaseTokenRetriever, FirebaseTokenRetriever>();
var assembly = Assembly.GetExecutingAssembly();
var environment = GetCurrentEnvironment();
var fileName = $"{assembly.GetName().Name}.appsettings.{environment}.json";
using var stream = assembly.GetManifestResourceStream(fileName);
var configBuilder = new ConfigurationBuilder()
.AddJsonStream(stream);
var configuration = configBuilder.Build();
// Load settings into ApiSettings object
var apiSettings = configuration.GetSection("ApiSettings").Get<ApiSettings>();
if (apiSettings == null)
{
throw new Exception("Api Settings not available");
}
// Register ApiSettings instance with DI
builder.Services.AddSingleton(apiSettings);
builder.Services.AddMauiBlazorWebView();
//builder.Services.AddMudServices();
builder.Services.AddRadzenComponents();
//builder.Services.AddTransient<MainPageViewModel>();
//builder.Services.AddTransient<MainPage>();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif
//builder.Services
//.AddViews()
//.AddViewModels()
//.AddCustomServices(config);
return builder.Build();
}
private static string GetCurrentEnvironment()
{
#if DEBUG
return "Development";
#else
return "Production";
#endif
}
private static MauiAppBuilder RegisterFirebaseServices(this MauiAppBuilder builder)
{
try
{
builder.ConfigureLifecycleEvents(events => {
#if IOS
events.AddiOS(iOS => iOS.WillFinishLaunching((app, launchOptions) => {
CrossFirebase.Initialize();
FirebaseCloudMessagingImplementation.Initialize();
return true;
}));
#elif ANDROID
events.AddAndroid(android => android.OnCreate((activity, _) =>
{
CrossFirebase.Initialize(activity);
//FirebaseAnalyticsImplementation.Initialize(activity);
}));
#endif
});
//CrossFirebaseCloudMessaging.Current.NotificationTapped += Current_NotificationTapped;
CrossFirebaseCloudMessaging.Current.TokenChanged += FcmTokenChanged;
return builder;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return builder;
}
}
private static void FcmTokenChanged(object? sender, FCMTokenChangedEventArgs e)
{
}
private static void Current_NotificationTapped(object? sender, FCMNotificationTappedEventArgs e)
{
try
{
var notificationData = e?.Notification?.Data;
if (notificationData != null && notificationData.ContainsKey("link"))
{
//Do something with the link
}
}
catch(Exception exc) {
Console.WriteLine(exc.ToString());
}
}
}
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.myapp.myapp" android:versionCode="1">
<application android:allowBackup="true" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config" android:label="myapp">
<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="true" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.myapp.myapp" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="34" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
</manifest>