Shaunebu.MAUI.FirebasePushNotifications 1.0.5

dotnet add package Shaunebu.MAUI.FirebasePushNotifications --version 1.0.5
                    
NuGet\Install-Package Shaunebu.MAUI.FirebasePushNotifications -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shaunebu.MAUI.FirebasePushNotifications" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shaunebu.MAUI.FirebasePushNotifications" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="Shaunebu.MAUI.FirebasePushNotifications" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shaunebu.MAUI.FirebasePushNotifications --version 1.0.5
                    
#r "nuget: Shaunebu.MAUI.FirebasePushNotifications, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shaunebu.MAUI.FirebasePushNotifications@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shaunebu.MAUI.FirebasePushNotifications&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=Shaunebu.MAUI.FirebasePushNotifications&version=1.0.5
                    
Install as a Cake Tool

Shaunebu.MAUI.FirebasePushNotifications πŸ””πŸ”₯

Platform MAUI License Status

Resilience Observability

NuGet Version

Android iOS Enterprise

Support

Overview ✨


Shaunebu.MAUI.FirebasePushNotifications is an enterprise-grade Firebase Cloud Messaging (FCM) integration for .NET MAUI (Android & iOS) with:

  • First-class MAUI & DI integration

  • Enterprise observability (logging + telemetry hooks)

  • Cancellation-friendly APIs (all key operations support CancellationToken)

  • Robust validation & error handling (google-services.json / GoogleService-Info.plist)

  • Unified API for:

    • Token registration / unregistration

    • Topic subscription management

    • Notification categories & actions

    • Notification channel management (Android)

    • Foreground & background message processing

It is heavily inspired by Plugin.FirebasePushNotifications but modernized and extended for:

  • .NET 10 + modern MAUI

  • Enterprise scenarios (telemetry, DI, graceful cancellation, recovery)

  • Real-world production constraints (iOS 18 quirks, config validation, state recovery)

Feature Comparison πŸ†š


Libraries Compared

  • Shaunebu.MAUI.FirebasePushNotifications (this library)

  • Plugin.FirebasePushNotifications by Thomas Galliker

  • Shiny.Push (Shiny ecosystem)

  • Raw Firebase SDK (manual bindings & platform code)

The goal of Shaunebu’s library is not just β€œyet another wrapper”, but a modern, MAUI-first, enterprise-friendly abstraction.

High-Level Comparison

Feature / Aspect Shaunebu.MAUI.FirebasePushNotifications Plugin.FirebasePushNotifications Shiny.Push Raw Firebase SDK
Target Platforms βœ… MAUI Android & iOS (.NET 10 ready) βœ… Xamarin / MAUI (depends on branch) βœ… MAUI / Xamarin βœ… Native per platform
Unified API βœ… Single cross-platform API βœ… Yes βœ… Yes (via Shiny) ❌ You implement yourself
Token Registration / Unregistration βœ… With CancellationToken overloads βœ… Basic async βœ… Async ❌ Manual
Topic Management βœ… Subscribe/Unsubscribe/All with CT & recovery βœ… Yes βœ… Yes ❌ Manual
Notification Categories (iOS) βœ… Strongly typed categories & actions βœ… Yes βœ… Yes ❌ Manual UNNotificationCenter usage
Notification Channels (Android) βœ… Channel groups + default channel guarantee βœ… Yes βœ… Yes ❌ Manual NotificationChannel setup
MAUI Lifecycle Integration βœ… UseFirebasePushNotifications extension ⚠️ Usually manual setup ⚠️ Via Shiny bootstrap ❌ Completely manual
Configuration Validation βœ… Validates google-services.json & GoogleService-Info.plist with rich exceptions ⚠️ Partial / implicit ⚠️ Some ❌ You must debug yourself
Error Handling βœ… Dedicated FirebaseAppInitializationException + descriptive messages ⚠️ Basic ⚠️ Depends ❌ Raw exceptions
State Recovery βœ… RecoverStateAsync() (token & topic state) ❌ No explicit API ⚠️ Framework-specific ❌ You build it
Cancellation Support βœ… All core async APIs have CT overloads ❌ No ⚠️ Partial ❌ Up to you
Telemetry Hooks βœ… IFirebasePushTelemetry (TrackEvent / TrackError) ❌ ⚠️ Use Shiny telemetry ❌ You wire telemetry manually
Logging Integration βœ… ILogger everywhere (nullable logger supported) ⚠️ Some logging ⚠️ Shiny logging ❌ Manual
iOS 18 / APNs Workarounds βœ… iOS 18 notification rate limiter workaround ❌ ❌ (or custom) ❌ Manual
Notification Handling Abstraction βœ… IPushNotificationHandler per platform βœ… Similar βœ… Uses Shiny β€œjobs/handlers” ❌ All in platform code
DI-Friendly Design βœ… All components registered via MauiAppBuilder & IServiceCollection ⚠️ Mixed βœ… Strong ❌ You wire everything
Production Hardening βœ… Validation + telemetry + recovery + cancellation ⚠️ Solid but older stack βœ… Good but Shiny-centric ❌ You must design it
Learning Curve βœ… MAUI-idiomatic, one entry-point ⚠️ Good docs but Xamarin-styled ⚠️ Must adopt Shiny ecosystem 🚧 High (native FCM & APNs)

Installation πŸ“¦


dotnet add package Shaunebu.MAUI.FirebasePushNotifications

Or via PackageReference:

<ItemGroup>
  <PackageReference Include="Shaunebu.MAUI.FirebasePushNotifications" Version="1.0.0" />
</ItemGroup>

Quick Start πŸš€


1. Configure in MauiProgram.cs

using Shaunebu.MAUI.FirebasePushNotifications;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        // ...
        builder
            .UseMauiApp<App>()
            .UseFirebasePushNotifications(options =>
            {
                // Global options
                options.AutoInitEnabled = true;

                // Android configuration
                options.Android.NotificationActivityType = typeof(MainActivity);
                // You can also configure channels/groups here (see below)

                // iOS configuration
                options.iOS.PresentationOptions =
                    UNNotificationPresentationOptions.Alert |
                    UNNotificationPresentationOptions.Badge |
                    UNNotificationPresentationOptions.Sound;

                // Optional: iOS 18 workaround
                options.iOS.iOS18Workaround.Enabled = true;
            });

        // Register your custom handler (optional but recommended)
        builder.Services.AddSingleton<IPushNotificationHandler, MyPushNotificationHandler>();

        return builder.Build();
    }
}

Under the hood this wires lifecycle events, DI registrations, notification channels (Android), and the cross-platform singleton instance.


2. Implement a Notification Handler

using Shaunebu.MAUI.FirebasePushNotifications.Abstractions;
using Shaunebu.MAUI.FirebasePushNotifications.Enums;

public class MyPushNotificationHandler : IPushNotificationHandler
{
    private readonly ILogger<MyPushNotificationHandler> _logger;

    public MyPushNotificationHandler(ILogger<MyPushNotificationHandler> logger)
        => _logger = logger;

    public Task OnNotificationReceivedAsync(IDictionary<string, object> data,
                                            NotificationCategoryType categoryType)
    {
        _logger.LogInformation("Notification received with category {Category}", categoryType);

        // Example: navigate to a page or show an in-app dialog
        // ...

        return Task.CompletedTask;
    }

    public Task OnNotificationOpenedAsync(IDictionary<string, object> data,
                                          NotificationCategoryType categoryType)
    {
        _logger.LogInformation("Notification opened. Category: {Category}", categoryType);

        // Handle deep link, navigation, analytics, etc.
        return Task.CompletedTask;
    }

    public Task OnNotificationActionAsync(IDictionary<string, object> data,
                                          string categoryId,
                                          string actionId,
                                          NotificationCategoryType categoryType)
    {
        _logger.LogInformation("Action clicked: {Category}/{Action}", categoryId, actionId);
        // Handle custom actions (e.g. "ACCEPT_ORDER", "MARK_AS_READ")
        return Task.CompletedTask;
    }
}

3. Register/Unregister for Push Notifications

using Shaunebu.MAUI.FirebasePushNotifications;

public partial class MainPage : ContentPage
{
    private readonly IFirebasePushNotification _push;

    public MainPage(IFirebasePushNotification push)
    {
        InitializeComponent();
        _push = push;
    }

    private async void OnRegisterClicked(object sender, EventArgs e)
    {
        await _push.RegisterForPushNotificationsAsync();
        var token = _push.Token;

        await DisplayAlert("FCM Token", token ?? "<null>", "OK");
    }

    private async void OnUnregisterClicked(object sender, EventArgs e)
    {
        await _push.UnregisterForPushNotificationsAsync();
        await DisplayAlert("FCM", "Unregistered from push notifications", "OK");
    }
}

🚨 Deep Configuration Validation (Android + iOS)

Enterprise-grade validation β€” unique to this library

Before initializing Firebase, the library executes a full validation pipeline.


Android Validation

Your library checks:

Validation Description
google-services.json presence Ensures file exists under Platforms/Android
Build Action = GoogleServicesJson Mandatory for FCM resource merging
google_app_id exists Extracted from merged Android resources
JSON structure integrity project_id, project_number, mobilesdk_app_id, api key
Package name match Compares JSON package vs MAUI manifest package
FirebaseOptions override validation Ensures consistency with JSON
Telemetry reporting Errors routed to IFirebasePushTelemetry

Failure Example:

Invalid google-services.json:
Expected package "com.company.app"
Found            "com.company.dev"

iOS Validation

Validation Description
GoogleService-Info.plist presence Must be included as BundleResource
Bundle identifier match plist BUNDLE_ID must match app bundle
Required keys GOOGLE_APP_ID, GCM_SENDER_ID, API_KEY
FirebaseOptions override validation Required fields validated
Telemetry reporting Errors logged and telemetered

Failure Example:

MissingGoogleServiceInfoPlist:
Ensure GoogleService-Info.plist is included with Build Action 'BundleResource'.

Strict Mode (Optional)

options.Validation.StrictMode = true;

Strict mode throws immediately on:

  • bundle mismatches

  • package mismatches

  • missing JSON/PLIST

  • invalid FirebaseOptions


πŸ“¦ Payload Normalization Engine (Android + iOS)

The library converts any FCM payload into a normalized flat dictionary:

IDictionary<string, object> data;

Android Supports:

βœ” Java primitives
βœ” Java.Lang.Object[]
βœ” Nested Bundle β†’ flatten
βœ” ArrayList β†’ object[]
βœ” Null-safe fallbacks
βœ” Key prefixing (aps.alert.title)

iOS Supports:

βœ” NSDictionary recursion
βœ” APS parsing
βœ” APS.Alert flattening
βœ” String-safe conversion Example Final Output:

{
  ["aps.alert.title"] = "Hello",
  ["aps.alert.body"]  = "Welcome!",
  ["type"]            = "order",
  ["order_id"]        = "123"
}

This is critical for analytics, routing, A/B testing, and multi-platform consistency.


πŸ” Security & Hardening

Your library provides:

βœ” Configuration validation (JSON/PLIST)

βœ” Token & topic state recovery

βœ” Telemetry audit trail

βœ” Sandboxed type conversion

βœ” Cancel-safe async operations

βœ” Optional Strict Mode

βœ” iOS 18 rate-limiter protections

These features are enterprise-critical and unique among MAUI FCM libraries.


πŸ— Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Shaunebu.MAUI.FirebasePushNotifications                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Cross-Platform Layer                                                 β”‚
β”‚  - IFirebasePushNotification                                         β”‚
β”‚  - IPushNotificationHandler                                          β”‚
β”‚  - IFirebasePushTelemetry                                            β”‚
β”‚  - Validation Engine                                                 β”‚
β”‚  - Token & Topic Recovery                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Android Implementation         β”‚ iOS Implementation                  β”‚
β”‚  - FirebaseAppHelper           β”‚ - FirebaseAppHelper                 β”‚
β”‚  - NotificationChannels        β”‚ - UNUserNotificationCenter Delegate β”‚
β”‚  - Intent Processor            β”‚ - MessagingDelegateImpl             β”‚
β”‚  - Bundle Parser               β”‚ - APS/NSDictionary Parser           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”₯ Advanced Usage Scenarios

βœ” Server-driven navigation

Use payload keys like route, screen, or nested routing metadata.

βœ” Multi-tenant topic structures

tenant/{id}/orders
tenant/{id}/notifications

βœ” Dynamic configuration

Override FirebaseOptions in runtime:

  • white-label apps

  • feature-flagged environments

βœ” Enriched telemetry

telemetry.TrackEvent("Push.Opened", new { topic, category, openedAt = Now });

βœ” Offline-first behavior

RecoverStateAsync ensures durability after restarts.

Core Concepts & APIs 🎯


IFirebasePushNotification

Core cross-platform interface you consume in your app:

  • Task RegisterForPushNotificationsAsync()

  • Task RegisterForPushNotificationsAsync(CancellationToken ct)

  • Task UnregisterForPushNotificationsAsync()

  • Task UnregisterForPushNotificationsAsync(CancellationToken ct)

  • Task SubscribeTopicAsync(string topic)

  • Task SubscribeTopicAsync(string topic, CancellationToken ct)

  • Task SubscribeTopicsAsync(string[] topics)

  • Task SubscribeTopicsAsync(string[] topics, CancellationToken ct)

  • Task UnsubscribeTopicAsync(string topic)

  • Task UnsubscribeTopicAsync(string topic, CancellationToken ct)

  • Task UnsubscribeTopicsAsync(string[] topics)

  • Task UnsubscribeTopicsAsync(string[] topics, CancellationToken ct)

  • Task UnsubscribeAllTopicsAsync()

  • Task UnsubscribeAllTopicsAsync(CancellationToken ct)

  • string Token { get; } (FCM token)

  • Task RecoverStateAsync() (re-apply token and topic state after app restart / re-config)

All major operations have a CancellationToken-aware overload, which is important for enterprise and long-running operations, especially around registration and topic management.


FirebasePushNotificationOptions

Central configuration object passed via UseFirebasePushNotifications:

  • bool AutoInitEnabled

  • FirebasePushNotificationAndroidOptions Android { get; }

  • FirebasePushNotificationiOSOptions iOS { get; }

Android options (highlights):

  • Type NotificationActivityType β€” Activity used to handle notification intents

  • NotificationChannelGroup[] NotificationChannelGroups

  • NotificationChannel[] NotificationChannels

  • FirebaseOptions? FirebaseOptions β€” programmatic override instead of google-services.json

iOS options (highlights):

  • Firebase.Core.Options? FirebaseOptions β€” programmatic override instead of GoogleService-Info.plist

  • UNNotificationPresentationOptions PresentationOptions

  • iOS18WorkaroundOptions iOS18Workaround (rate-limit repeated WillPresentNotification calls)


Telemetry Integration: IFirebasePushTelemetry πŸ“Š

To integrate with Application Insights, OpenTelemetry, custom dashboards, etc., implement:

public interface IFirebasePushTelemetry
{
    void TrackEvent(string name, IDictionary<string, string?>? properties = null);
    void TrackEvent(string name, IDictionary<string, object?>? properties = null);
    void TrackError(Exception exception, IDictionary<string, string?>? properties = null);
}

Example: Application Insights Telemetry

public class AppInsightsFirebaseTelemetry : IFirebasePushTelemetry
{
    private readonly TelemetryClient _client;

    public AppInsightsFirebaseTelemetry(TelemetryClient client)
        => _client = client;

    public void TrackEvent(string name, IDictionary<string, string?>? properties = null)
        => _client.TrackEvent(name, properties);

    public void TrackEvent(string name, IDictionary<string, object?>? properties = null)
    {
        var stringProps = properties?.ToDictionary(
            kvp => kvp.Key,
            kvp => kvp.Value?.ToString());

        _client.TrackEvent(name, stringProps);
    }

    public void TrackError(Exception exception, IDictionary<string, string?>? properties = null)
        => _client.TrackException(exception, properties);
}

Register it:

builder.Services.AddSingleton<IFirebasePushTelemetry, AppInsightsFirebaseTelemetry>();

The library will emit events like:

  • "Firebase.Initialize.Start", "Firebase.Initialize.Success"

  • "Firebase.Token.Received"

  • "Firebase.Topic.Subscribe"

  • "Firebase.Intent.Processed"

  • And will call TrackError on failures in registration, initialization, etc.

Android Feature Highlights πŸ€–


1. Configuration Validation

Before doing any heavy FCM operations, Android will:

  • Ensure google_app_id resource exists

  • Ensure it is non-empty

  • If issues are detected, it throws FirebaseAppInitializationException with:

    • Message explaining what is wrong

    • Logged via ILogger

    • Optionally reported via IFirebasePushTelemetry.TrackError

2. Firebase Initialization Flow

  • Uses FirebaseAppHelper.IsFirebaseAppInitialized(context) to check if FCM is already set up

  • If not initialized:

    • If Android.FirebaseOptions is provided β†’ configure from code

    • Else β†’ FirebaseApp.InitializeApp(context) (from google-services.json)

  • Ensures initialization succeeded, or throws a rich exception

3. Notification Channels

  • Uses INotificationChannels abstraction (default NotificationChannels implementation)

  • You can configure:

    • Channel groups (NotificationChannelGroups)

    • Channels (NotificationChannels)

  • If no channels are configured, it ensures a default channel is created so Android 8+ behaves correctly.

4. Intent Processing

ProcessIntent(Activity activity, Intent intent):

  • Automatically wired via MAUI lifecycle (OnCreate, OnNewIntent) in UseFirebasePushNotifications

  • Performs safety checks:

    • Skips FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

    • Skips Intent.ActionMain

  • Checks extras and:

    • Cancels the related notification when tapped

    • Calls HandleNotificationOpened or HandleNotificationAction depending on category/action

  • Sends telemetry: "Firebase.Intent.Processed"

5. Topic Management (With & Without CancellationToken)

// Simple
await _push.SubscribeTopicAsync("news");
await _push.UnsubscribeTopicAsync("news");

// With CancellationToken
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await _push.SubscribeTopicAsync("news", cts.Token);

Android internally:

  • Uses TaskCompletionSource + Firebase OnCompleteListener

  • Honors CancellationToken by cancelling the TaskCompletionSource

  • Updates SubscribedTopics preference atomically

iOS Feature Highlights 🍏


1. Configuration Validation

ValidateFirebaseConfiguration():

  • If using iOS.FirebaseOptions (programmatic):

    • Checks GoogleAppId & ProjectId are non-empty
  • Else:

    • Expects GoogleService-Info.plist in bundle with correct Build Action

    • If missing β†’ throws MissingGoogleServiceInfoPlist (FirebaseAppInitializationException)

2. Firebase & APNs Initialization

  • Configures Firebase (Firebase.Core.App.Configure)

  • Verifies messaging is initialized (Messaging.SharedInstance)

  • Wire up:

    • UNUserNotificationCenter.Current.Delegate (custom delegate)

    • Firebase.CloudMessaging.Messaging.SharedInstance.Delegate to MessagingDelegateImpl

3. Notification Presentation (iOS 14+ & iOS 18 Workaround)

  • Maps priority from payload (e.g. "high", "low") into UNNotificationPresentationOptions

  • iOS 18 workaround:

    • Prevents repeated WillPresentNotification calls causing spam

    • Uses NotificationRateLimiter with configurable expiration time

4. Token Handling

  • RegisteredForRemoteNotifications(NSData deviceToken):

    • Assigns APNs token to FCM

    • Calls DidReceiveRegistrationToken

  • DidReceiveRegistrationToken(string fcmToken):

    • Calls HandleTokenRefresh(fcmToken) in base

    • Persists token and triggers re-subscription of pending topics

5. Topic Subscription (With Pending Queue)

  • If APNs token is not ready:

    • Topic operations are queued in pendingTopics

    • Automatically flushed when DidReceiveRegistrationToken executes

  • Supports CT overloads with WaitAsync(cancellationToken) for SubscribeAsync / UnsubscribeAsync

MAUI Integration Details πŸ“±


Lifecycle Events

UseFirebasePushNotifications hooks into platform lifecycle:

  • iOS

    • FinishedLaunching
      • Resolves IPushNotificationHandler

      • Ensures IFirebasePushNotification.Current is instantiated

  • Android

    • OnCreate & OnNewIntent
      • Resolves IPushNotificationHandler if not already set

      • Calls ProcessIntent(activity, intent) to handle taps & deep links

Service Registrations

UseFirebasePushNotifications also wires:

// Cross-platform
builder.Services.AddSingleton(_ => IFirebasePushNotification.Current);
builder.Services.AddSingleton(_ => INotificationPermissions.Current);
builder.Services.TryAddSingleton<IFirebasePushNotificationPreferences, FirebasePushNotificationPreferences>();
builder.Services.TryAddSingleton<IPreferences>(_ => Preferences.Default);
builder.Services.AddSingleton(defaultOptions);

// Android
builder.Services.AddSingleton(c => INotificationChannels.Current);
builder.Services.TryAddSingleton<INotificationBuilder, NotificationBuilder>();

// iOS
builder.Services.AddSingleton<INotificationChannels, NotificationChannels>();

Sample Usage Scenarios 🎨


1. Subscribe a User to Profile-Based Topics

public class UserNotificationService
{
    private readonly IFirebasePushNotification _push;

    public UserNotificationService(IFirebasePushNotification push)
    {
        _push = push;
    }

    public async Task UpdateSubscriptionsAsync(UserProfile profile, CancellationToken ct = default)
    {
        var topics = new List<string>();

        if (profile.IsPremium)
            topics.Add("premium");

        if (profile.Region is { } region)
            topics.Add($"region_{region.ToLowerInvariant()}");

        if (profile.AllowsMarketing)
            topics.Add("marketing");

        await _push.UnsubscribeAllTopicsAsync(ct);
        await _push.SubscribeTopicsAsync(topics.ToArray(), ct);
    }
}

2. Deep Linking into Pages on Notification Open

public class NavigationPushHandler : IPushNotificationHandler
{
    private readonly Shell _shell;

    public NavigationPushHandler()
    {
        _shell = (Shell)Application.Current.MainPage;
    }

    public Task OnNotificationReceivedAsync(IDictionary<string, object> data,
                                            NotificationCategoryType categoryType)
        => Task.CompletedTask;

    public async Task OnNotificationOpenedAsync(IDictionary<string, object> data,
                                                NotificationCategoryType categoryType)
    {
        if (data.TryGetValue("route", out var routeObj) &&
            routeObj is string route &&
            !string.IsNullOrWhiteSpace(route))
        {
            await _shell.GoToAsync(route);
        }
    }

    public Task OnNotificationActionAsync(IDictionary<string, object> data,
                                          string categoryId,
                                          string actionId,
                                          NotificationCategoryType categoryType)
    {
        // Handle button actions
        return Task.CompletedTask;
    }
}

3. Integrating Telemetry & Logs for Production

builder.Logging
    .AddDebug()
    .AddConsole();

builder.Services.AddSingleton<IFirebasePushTelemetry, AppInsightsFirebaseTelemetry>();

The library then:

  • Logs detailed debug info when LogLevel.Debug is enabled

  • Emits telemetry for:

    • Initialization successes/failures

    • Token fetching

    • Topic subscription/unsubscription

    • Intent processing and platform operations

Troubleshooting πŸ”§


Missing google-services.json (Android)

Symptoms:

  • Startup throws FirebaseAppInitializationException

  • Log message about google_app_id not found

Checklist:

  • google-services.json placed under Platforms/Android/

  • Build action set to GoogleServicesJson

  • Package name in JSON matches your MAUI Android package (manifest / ApplicationId)


Missing GoogleService-Info.plist (iOS)

Symptoms:

  • App throws MissingGoogleServiceInfoPlist

  • Logs mention incorrect Build Action

Checklist:

  • GoogleService-Info.plist included in iOS project

  • Build Action: BundleResource

  • Bundle ID in the plist matches your app bundle


Notifications Not Arriving in Foreground (iOS)

  • Check options.iOS.PresentationOptions (alerts/banners/list)

  • Ensure UNUserNotificationCenter delegate is not overwritten by your app
    (if you need a custom delegate, you must integrate with the library’s delegate)


Topics Not Subscribed (iOS)

  • Ensure APNs token is available (some operations are queued until token is ready)

  • You can debug pendingTopics by enabling LogLevel.Debug

  • Check for typos or empty topic names (library will throw for empty names)

API Reference (High-Level) πŸ“š


IFirebasePushNotification

Member Description
Task RegisterForPushNotificationsAsync() Configure platform, request permissions and register for FCM push notifications
Task RegisterForPushNotificationsAsync(CancellationToken) CT-aware overload
Task UnregisterForPushNotificationsAsync() Disable AutoInit, unregister push, clear token
Task UnregisterForPushNotificationsAsync(CancellationToken) CT-aware
Task SubscribeTopicAsync(string) Subscribe to a single FCM topic
Task SubscribeTopicAsync(string, CancellationToken) CT-aware
Task SubscribeTopicsAsync(string[]) Subscribe to multiple topics
Task SubscribeTopicsAsync(string[], CancellationToken) CT-aware
Task UnsubscribeTopicAsync(string) Unsubscribe from topic
Task UnsubscribeTopicAsync(string, CancellationToken) CT-aware
Task UnsubscribeTopicsAsync(string[]) Multiple topics
Task UnsubscribeTopicsAsync(string[], CancellationToken) CT-aware
Task UnsubscribeAllTopicsAsync() Clear all topics
Task UnsubscribeAllTopicsAsync(CancellationToken) CT-aware
Task RecoverStateAsync() Re-apply stored token/topics after restart
string Token { get; } Current FCM token (if available)

Migration Guide 🚚


Migrating from Plugin.FirebasePushNotifications

Conceptually very similar, but:

  • Register via UseFirebasePushNotifications instead of manual CrossFirebasePushNotification bootstrap

  • Replace CrossFirebasePushNotification.Current with DI-injected IFirebasePushNotification

  • Update handlers to the new IPushNotificationHandler interface

  • Optional but recommended:

    • Use IFirebasePushTelemetry for events/errors

    • Use CT overloads for operations invoked from UI/viewmodels

Migrating from Raw Firebase SDK

You can remove:

  • Custom FirebaseMessagingService glue you created

  • Manual UNUserNotificationCenter delegate code (if you used FCM swizzling)

  • Custom preferences storage for token/topics

And instead:

  • Use UseFirebasePushNotifications + IFirebasePushNotification

  • Implement IPushNotificationHandler to handle your app-level behavior


License πŸ“„

This project is licensed under the MIT License. Shaunebu.MAUI.FirebasePushNotifications πŸ””πŸ”₯ Platform MAUI License Status

Resilience Observability

NuGet Version

Android iOS Enterprise

Support

Overview ✨ Shaunebu.MAUI.FirebasePushNotifications is an enterprise-grade Firebase Cloud Messaging (FCM) integration for .NET MAUI (Android & iOS) with:

First-class MAUI & DI integration

Enterprise observability (logging + telemetry hooks)

Cancellation-friendly APIs (all key operations support CancellationToken)

Robust validation & error handling (google-services.json / GoogleService-Info.plist)

Unified API for:

Token registration / unregistration

Topic subscription management

Notification categories & actions

Notification channel management (Android)

Foreground & background message processing

It is heavily inspired by Plugin.FirebasePushNotifications but modernized and extended for:

.NET 10 + modern MAUI

Enterprise scenarios (telemetry, DI, graceful cancellation, recovery)

Real-world production constraints (iOS 18 quirks, config validation, state recovery)

Feature Comparison πŸ†š Libraries Compared Shaunebu.MAUI.FirebasePushNotifications (this library)

Plugin.FirebasePushNotifications by Thomas Galliker

Shiny.Push (Shiny ecosystem)

Raw Firebase SDK (manual bindings & platform code)

The goal of Shaunebu’s library is not just β€œyet another wrapper”, but a modern, MAUI-first, enterprise-friendly abstraction.

High-Level Comparison Feature / Aspect Shaunebu.MAUI.FirebasePushNotifications Plugin.FirebasePushNotifications Shiny.Push Raw Firebase SDK Target Platforms βœ… MAUI Android & iOS (.NET 10 ready) βœ… Xamarin / MAUI (depends on branch) βœ… MAUI / Xamarin βœ… Native per platform Unified API βœ… Single cross-platform API βœ… Yes βœ… Yes (via Shiny) ❌ You implement yourself Token Registration / Unregistration βœ… With CancellationToken overloads βœ… Basic async βœ… Async ❌ Manual Topic Management βœ… Subscribe/Unsubscribe/All with CT & recovery βœ… Yes βœ… Yes ❌ Manual Notification Categories (iOS) βœ… Strongly typed categories & actions βœ… Yes βœ… Yes ❌ Manual UNNotificationCenter usage Notification Channels (Android) βœ… Channel groups + default channel guarantee βœ… Yes βœ… Yes ❌ Manual NotificationChannel setup MAUI Lifecycle Integration βœ… UseFirebasePushNotifications extension ⚠️ Usually manual setup ⚠️ Via Shiny bootstrap ❌ Completely manual Configuration Validation βœ… Validates google-services.json & GoogleService-Info.plist with rich exceptions ⚠️ Partial / implicit ⚠️ Some ❌ You must debug yourself Error Handling βœ… Dedicated FirebaseAppInitializationException + descriptive messages ⚠️ Basic ⚠️ Depends ❌ Raw exceptions State Recovery βœ… RecoverStateAsync() (token & topic state) ❌ No explicit API ⚠️ Framework-specific ❌ You build it Cancellation Support βœ… All core async APIs have CT overloads ❌ No ⚠️ Partial ❌ Up to you Telemetry Hooks βœ… IFirebasePushTelemetry (TrackEvent / TrackError) ❌ ⚠️ Use Shiny telemetry ❌ You wire telemetry manually Logging Integration βœ… ILogger everywhere (nullable logger supported) ⚠️ Some logging ⚠️ Shiny logging ❌ Manual iOS 18 / APNs Workarounds βœ… iOS 18 notification rate limiter workaround ❌ ❌ (or custom) ❌ Manual Notification Handling Abstraction βœ… IPushNotificationHandler per platform βœ… Similar βœ… Uses Shiny β€œjobs/handlers” ❌ All in platform code DI-Friendly Design βœ… All components registered via MauiAppBuilder & IServiceCollection ⚠️ Mixed βœ… Strong ❌ You wire everything Production Hardening βœ… Validation + telemetry + recovery + cancellation ⚠️ Solid but older stack βœ… Good but Shiny-centric ❌ You must design it Learning Curve βœ… MAUI-idiomatic, one entry-point ⚠️ Good docs but Xamarin-styled ⚠️ Must adopt Shiny ecosystem 🚧 High (native FCM & APNs) Installation πŸ“¦

dotnet add package Shaunebu.MAUI.FirebasePushNotifications Or via PackageReference:

<ItemGroup> <PackageReference Include="Shaunebu.MAUI.FirebasePushNotifications" Version="1.0.0" /> </ItemGroup> Quick Start πŸš€

  1. Configure in MauiProgram.cs

using Shaunebu.MAUI.FirebasePushNotifications;

public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder();

    // ...
    builder
        .UseMauiApp<App>()
        .UseFirebasePushNotifications(options =>
        {
            // Global options
            options.AutoInitEnabled = true;

            // Android configuration
            options.Android.NotificationActivityType = typeof(MainActivity);
            // You can also configure channels/groups here (see below)

            // iOS configuration
            options.iOS.PresentationOptions =
                UNNotificationPresentationOptions.Alert |
                UNNotificationPresentationOptions.Badge |
                UNNotificationPresentationOptions.Sound;

            // Optional: iOS 18 workaround
            options.iOS.iOS18Workaround.Enabled = true;
        });

    // Register your custom handler (optional but recommended)
    builder.Services.AddSingleton<IPushNotificationHandler, MyPushNotificationHandler>();

    return builder.Build();
}

} Under the hood this wires lifecycle events, DI registrations, notification channels (Android), and the cross-platform singleton instance.

  1. Implement a Notification Handler

using Shaunebu.MAUI.FirebasePushNotifications.Abstractions; using Shaunebu.MAUI.FirebasePushNotifications.Enums;

public class MyPushNotificationHandler : IPushNotificationHandler { private readonly ILogger<MyPushNotificationHandler> _logger;

public MyPushNotificationHandler(ILogger<MyPushNotificationHandler> logger)
    => _logger = logger;

public Task OnNotificationReceivedAsync(IDictionary<string, object> data,
                                        NotificationCategoryType categoryType)
{
    _logger.LogInformation("Notification received with category {Category}", categoryType);

    // Example: navigate to a page or show an in-app dialog
    // ...

    return Task.CompletedTask;
}

public Task OnNotificationOpenedAsync(IDictionary<string, object> data,
                                      NotificationCategoryType categoryType)
{
    _logger.LogInformation("Notification opened. Category: {Category}", categoryType);

    // Handle deep link, navigation, analytics, etc.
    return Task.CompletedTask;
}

public Task OnNotificationActionAsync(IDictionary<string, object> data,
                                      string categoryId,
                                      string actionId,
                                      NotificationCategoryType categoryType)
{
    _logger.LogInformation("Action clicked: {Category}/{Action}", categoryId, actionId);
    // Handle custom actions (e.g. "ACCEPT_ORDER", "MARK_AS_READ")
    return Task.CompletedTask;
}

} 3. Register/Unregister for Push Notifications

using Shaunebu.MAUI.FirebasePushNotifications;

public partial class MainPage : ContentPage { private readonly IFirebasePushNotification _push;

public MainPage(IFirebasePushNotification push)
{
    InitializeComponent();
    _push = push;
}

private async void OnRegisterClicked(object sender, EventArgs e)
{
    await _push.RegisterForPushNotificationsAsync();
    var token = _push.Token;

    await DisplayAlert("FCM Token", token ?? "<null>", "OK");
}

private async void OnUnregisterClicked(object sender, EventArgs e)
{
    await _push.UnregisterForPushNotificationsAsync();
    await DisplayAlert("FCM", "Unregistered from push notifications", "OK");
}

} 🚨 Deep Configuration Validation (Android + iOS) Enterprise-grade validation β€” unique to this library Before initializing Firebase, the library executes a full validation pipeline.

Android Validation Your library checks:

Validation Description google-services.json presence Ensures file exists under Platforms/Android Build Action = GoogleServicesJson Mandatory for FCM resource merging google_app_id exists Extracted from merged Android resources JSON structure integrity project_id, project_number, mobilesdk_app_id, api key Package name match Compares JSON package vs MAUI manifest package FirebaseOptions override validation Ensures consistency with JSON Telemetry reporting Errors routed to IFirebasePushTelemetry Failure Example:

Invalid google-services.json: Expected package "com.company.app" Found "com.company.dev" iOS Validation Validation Description GoogleService-Info.plist presence Must be included as BundleResource Bundle identifier match plist BUNDLE_ID must match app bundle Required keys GOOGLE_APP_ID, GCM_SENDER_ID, API_KEY FirebaseOptions override validation Required fields validated Telemetry reporting Errors logged and telemetered Failure Example:

MissingGoogleServiceInfoPlist: Ensure GoogleService-Info.plist is included with Build Action 'BundleResource'. Strict Mode (Optional)

options.Validation.StrictMode = true; Strict mode throws immediately on:

bundle mismatches

package mismatches

missing JSON/PLIST

invalid FirebaseOptions

πŸ“¦ Payload Normalization Engine (Android + iOS) The library converts any FCM payload into a normalized flat dictionary:

IDictionary<string, object> data; Android Supports: βœ” Java primitives βœ” Java.Lang.Object[] βœ” Nested Bundle β†’ flatten βœ” ArrayList β†’ object[] βœ” Null-safe fallbacks βœ” Key prefixing (aps.alert.title)

iOS Supports: βœ” NSDictionary recursion βœ” APS parsing βœ” APS.Alert flattening βœ” String-safe conversion Example Final Output:

{ ["aps.alert.title"] = "Hello", ["aps.alert.body"] = "Welcome!", ["type"] = "order", ["order_id"] = "123" } This is critical for analytics, routing, A/B testing, and multi-platform consistency.

πŸ” Security & Hardening Your library provides:

βœ” Configuration validation (JSON/PLIST) βœ” Token & topic state recovery βœ” Telemetry audit trail βœ” Sandboxed type conversion βœ” Cancel-safe async operations βœ” Optional Strict Mode βœ” iOS 18 rate-limiter protections These features are enterprise-critical and unique among MAUI FCM libraries.

πŸ— Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Shaunebu.MAUI.FirebasePushNotifications β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Cross-Platform Layer β”‚ β”‚ - IFirebasePushNotification β”‚ β”‚ - IPushNotificationHandler β”‚ β”‚ - IFirebasePushTelemetry β”‚ β”‚ - Validation Engine β”‚ β”‚ - Token & Topic Recovery β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Android Implementation β”‚ iOS Implementation β”‚ β”‚ - FirebaseAppHelper β”‚ - FirebaseAppHelper β”‚ β”‚ - NotificationChannels β”‚ - UNUserNotificationCenter Delegate β”‚ β”‚ - Intent Processor β”‚ - MessagingDelegateImpl β”‚ β”‚ - Bundle Parser β”‚ - APS/NSDictionary Parser β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ πŸ”₯ Advanced Usage Scenarios βœ” Server-driven navigation Use payload keys like route, screen, or nested routing metadata.

βœ” Multi-tenant topic structures

tenant/{id}/orders tenant/{id}/notifications βœ” Dynamic configuration Override FirebaseOptions in runtime:

white-label apps

feature-flagged environments

βœ” Enriched telemetry

telemetry.TrackEvent("Push.Opened", new { topic, category, openedAt = Now }); βœ” Offline-first behavior RecoverStateAsync ensures durability after restarts.

Core Concepts & APIs 🎯 IFirebasePushNotification Core cross-platform interface you consume in your app:

Task RegisterForPushNotificationsAsync()

Task RegisterForPushNotificationsAsync(CancellationToken ct)

Task UnregisterForPushNotificationsAsync()

Task UnregisterForPushNotificationsAsync(CancellationToken ct)

Task SubscribeTopicAsync(string topic)

Task SubscribeTopicAsync(string topic, CancellationToken ct)

Task SubscribeTopicsAsync(string[] topics)

Task SubscribeTopicsAsync(string[] topics, CancellationToken ct)

Task UnsubscribeTopicAsync(string topic)

Task UnsubscribeTopicAsync(string topic, CancellationToken ct)

Task UnsubscribeTopicsAsync(string[] topics)

Task UnsubscribeTopicsAsync(string[] topics, CancellationToken ct)

Task UnsubscribeAllTopicsAsync()

Task UnsubscribeAllTopicsAsync(CancellationToken ct)

string Token { get; } (FCM token)

Task RecoverStateAsync() (re-apply token and topic state after app restart / re-config)

All major operations have a CancellationToken-aware overload, which is important for enterprise and long-running operations, especially around registration and topic management.

FirebasePushNotificationOptions Central configuration object passed via UseFirebasePushNotifications:

bool AutoInitEnabled

FirebasePushNotificationAndroidOptions Android { get; }

FirebasePushNotificationiOSOptions iOS { get; }

Android options (highlights):

Type NotificationActivityType β€” Activity used to handle notification intents

NotificationChannelGroup[] NotificationChannelGroups

NotificationChannel[] NotificationChannels

FirebaseOptions? FirebaseOptions β€” programmatic override instead of google-services.json

iOS options (highlights):

Firebase.Core.Options? FirebaseOptions β€” programmatic override instead of GoogleService-Info.plist

UNNotificationPresentationOptions PresentationOptions

iOS18WorkaroundOptions iOS18Workaround (rate-limit repeated WillPresentNotification calls)

Telemetry Integration: IFirebasePushTelemetry πŸ“Š To integrate with Application Insights, OpenTelemetry, custom dashboards, etc., implement:

public interface IFirebasePushTelemetry { void TrackEvent(string name, IDictionary<string, string?>? properties = null); void TrackEvent(string name, IDictionary<string, object?>? properties = null); void TrackError(Exception exception, IDictionary<string, string?>? properties = null); } Example: Application Insights Telemetry

public class AppInsightsFirebaseTelemetry : IFirebasePushTelemetry { private readonly TelemetryClient _client;

public AppInsightsFirebaseTelemetry(TelemetryClient client)
    => _client = client;

public void TrackEvent(string name, IDictionary<string, string?>? properties = null)
    => _client.TrackEvent(name, properties);

public void TrackEvent(string name, IDictionary<string, object?>? properties = null)
{
    var stringProps = properties?.ToDictionary(
        kvp => kvp.Key,
        kvp => kvp.Value?.ToString());

    _client.TrackEvent(name, stringProps);
}

public void TrackError(Exception exception, IDictionary<string, string?>? properties = null)
    => _client.TrackException(exception, properties);

} Register it:

builder.Services.AddSingleton<IFirebasePushTelemetry, AppInsightsFirebaseTelemetry>(); The library will emit events like:

"Firebase.Initialize.Start", "Firebase.Initialize.Success"

"Firebase.Token.Received"

"Firebase.Topic.Subscribe"

"Firebase.Intent.Processed"

And will call TrackError on failures in registration, initialization, etc.

Android Feature Highlights πŸ€–

  1. Configuration Validation Before doing any heavy FCM operations, Android will:

Ensure google_app_id resource exists

Ensure it is non-empty

If issues are detected, it throws FirebaseAppInitializationException with:

Message explaining what is wrong

Logged via ILogger

Optionally reported via IFirebasePushTelemetry.TrackError

  1. Firebase Initialization Flow Uses FirebaseAppHelper.IsFirebaseAppInitialized(context) to check if FCM is already set up

If not initialized:

If Android.FirebaseOptions is provided β†’ configure from code

Else β†’ FirebaseApp.InitializeApp(context) (from google-services.json)

Ensures initialization succeeded, or throws a rich exception

  1. Notification Channels Uses INotificationChannels abstraction (default NotificationChannels implementation)

You can configure:

Channel groups (NotificationChannelGroups)

Channels (NotificationChannels)

If no channels are configured, it ensures a default channel is created so Android 8+ behaves correctly.

  1. Intent Processing ProcessIntent(Activity activity, Intent intent):

Automatically wired via MAUI lifecycle (OnCreate, OnNewIntent) in UseFirebasePushNotifications

Performs safety checks:

Skips FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

Skips Intent.ActionMain

Checks extras and:

Cancels the related notification when tapped

Calls HandleNotificationOpened or HandleNotificationAction depending on category/action

Sends telemetry: "Firebase.Intent.Processed"

  1. Topic Management (With & Without CancellationToken)

// Simple await _push.SubscribeTopicAsync("news"); await _push.UnsubscribeTopicAsync("news");

// With CancellationToken using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); await _push.SubscribeTopicAsync("news", cts.Token); Android internally:

Uses TaskCompletionSource + Firebase OnCompleteListener

Honors CancellationToken by cancelling the TaskCompletionSource

Updates SubscribedTopics preference atomically

iOS Feature Highlights 🍏

  1. Configuration Validation ValidateFirebaseConfiguration():

If using iOS.FirebaseOptions (programmatic):

Checks GoogleAppId & ProjectId are non-empty Else:

Expects GoogleService-Info.plist in bundle with correct Build Action

If missing β†’ throws MissingGoogleServiceInfoPlist (FirebaseAppInitializationException)

  1. Firebase & APNs Initialization Configures Firebase (Firebase.Core.App.Configure)

Verifies messaging is initialized (Messaging.SharedInstance)

Wire up:

UNUserNotificationCenter.Current.Delegate (custom delegate)

Firebase.CloudMessaging.Messaging.SharedInstance.Delegate to MessagingDelegateImpl

  1. Notification Presentation (iOS 14+ & iOS 18 Workaround) Maps priority from payload (e.g. "high", "low") into UNNotificationPresentationOptions

iOS 18 workaround:

Prevents repeated WillPresentNotification calls causing spam

Uses NotificationRateLimiter with configurable expiration time

  1. Token Handling RegisteredForRemoteNotifications(NSData deviceToken):

Assigns APNs token to FCM

Calls DidReceiveRegistrationToken

DidReceiveRegistrationToken(string fcmToken):

Calls HandleTokenRefresh(fcmToken) in base

Persists token and triggers re-subscription of pending topics

  1. Topic Subscription (With Pending Queue) If APNs token is not ready:

Topic operations are queued in pendingTopics

Automatically flushed when DidReceiveRegistrationToken executes

Supports CT overloads with WaitAsync(cancellationToken) for SubscribeAsync / UnsubscribeAsync

MAUI Integration Details πŸ“± Lifecycle Events UseFirebasePushNotifications hooks into platform lifecycle:

iOS

FinishedLaunching Resolves IPushNotificationHandler

Ensures IFirebasePushNotification.Current is instantiated

Android

OnCreate & OnNewIntent Resolves IPushNotificationHandler if not already set

Calls ProcessIntent(activity, intent) to handle taps & deep links

Service Registrations UseFirebasePushNotifications also wires:

// Cross-platform builder.Services.AddSingleton(_ β‡’ IFirebasePushNotification.Current); builder.Services.AddSingleton(_ β‡’ INotificationPermissions.Current); builder.Services.TryAddSingleton<IFirebasePushNotificationPreferences, FirebasePushNotificationPreferences>(); builder.Services.TryAddSingleton<IPreferences>(_ β‡’ Preferences.Default); builder.Services.AddSingleton(defaultOptions);

// Android builder.Services.AddSingleton(c β‡’ INotificationChannels.Current); builder.Services.TryAddSingleton<INotificationBuilder, NotificationBuilder>();

// iOS builder.Services.AddSingleton<INotificationChannels, NotificationChannels>(); Sample Usage Scenarios 🎨

  1. Subscribe a User to Profile-Based Topics

public class UserNotificationService { private readonly IFirebasePushNotification _push;

public UserNotificationService(IFirebasePushNotification push)
{
    _push = push;
}

public async Task UpdateSubscriptionsAsync(UserProfile profile, CancellationToken ct = default)
{
    var topics = new List<string>();

    if (profile.IsPremium)
        topics.Add("premium");

    if (profile.Region is { } region)
        topics.Add($"region_{region.ToLowerInvariant()}");

    if (profile.AllowsMarketing)
        topics.Add("marketing");

    await _push.UnsubscribeAllTopicsAsync(ct);
    await _push.SubscribeTopicsAsync(topics.ToArray(), ct);
}

} 2. Deep Linking into Pages on Notification Open

public class NavigationPushHandler : IPushNotificationHandler { private readonly Shell _shell;

public NavigationPushHandler()
{
    _shell = (Shell)Application.Current.MainPage;
}

public Task OnNotificationReceivedAsync(IDictionary<string, object> data,
                                        NotificationCategoryType categoryType)
    => Task.CompletedTask;

public async Task OnNotificationOpenedAsync(IDictionary<string, object> data,
                                            NotificationCategoryType categoryType)
{
    if (data.TryGetValue("route", out var routeObj) &&
        routeObj is string route &&
        !string.IsNullOrWhiteSpace(route))
    {
        await _shell.GoToAsync(route);
    }
}

public Task OnNotificationActionAsync(IDictionary<string, object> data,
                                      string categoryId,
                                      string actionId,
                                      NotificationCategoryType categoryType)
{
    // Handle button actions
    return Task.CompletedTask;
}

} 3. Integrating Telemetry & Logs for Production

builder.Logging .AddDebug() .AddConsole();

builder.Services.AddSingleton<IFirebasePushTelemetry, AppInsightsFirebaseTelemetry>(); The library then:

Logs detailed debug info when LogLevel.Debug is enabled

Emits telemetry for:

Initialization successes/failures

Token fetching

Topic subscription/unsubscription

Intent processing and platform operations

Troubleshooting πŸ”§ Missing google-services.json (Android) Symptoms:

Startup throws FirebaseAppInitializationException

Log message about google_app_id not found

Checklist:

google-services.json placed under Platforms/Android/

Build action set to GoogleServicesJson

Package name in JSON matches your MAUI Android package (manifest / ApplicationId)

Missing GoogleService-Info.plist (iOS) Symptoms:

App throws MissingGoogleServiceInfoPlist

Logs mention incorrect Build Action

Checklist:

GoogleService-Info.plist included in iOS project

Build Action: BundleResource

Bundle ID in the plist matches your app bundle

Notifications Not Arriving in Foreground (iOS) Check options.iOS.PresentationOptions (alerts/banners/list)

Ensure UNUserNotificationCenter delegate is not overwritten by your app (if you need a custom delegate, you must integrate with the library’s delegate)

Topics Not Subscribed (iOS) Ensure APNs token is available (some operations are queued until token is ready)

You can debug pendingTopics by enabling LogLevel.Debug

Check for typos or empty topic names (library will throw for empty names)

API Reference (High-Level) πŸ“š IFirebasePushNotification Member Description Task RegisterForPushNotificationsAsync() Configure platform, request permissions and register for FCM push notifications Task RegisterForPushNotificationsAsync(CancellationToken) CT-aware overload Task UnregisterForPushNotificationsAsync() Disable AutoInit, unregister push, clear token Task UnregisterForPushNotificationsAsync(CancellationToken) CT-aware Task SubscribeTopicAsync(string) Subscribe to a single FCM topic Task SubscribeTopicAsync(string, CancellationToken) CT-aware Task SubscribeTopicsAsync(string[]) Subscribe to multiple topics Task SubscribeTopicsAsync(string[], CancellationToken) CT-aware Task UnsubscribeTopicAsync(string) Unsubscribe from topic Task UnsubscribeTopicAsync(string, CancellationToken) CT-aware Task UnsubscribeTopicsAsync(string[]) Multiple topics Task UnsubscribeTopicsAsync(string[], CancellationToken) CT-aware Task UnsubscribeAllTopicsAsync() Clear all topics Task UnsubscribeAllTopicsAsync(CancellationToken) CT-aware Task RecoverStateAsync() Re-apply stored token/topics after restart string Token { get; } Current FCM token (if available) Migration Guide 🚚 Migrating from Plugin.FirebasePushNotifications Conceptually very similar, but:

Register via UseFirebasePushNotifications instead of manual CrossFirebasePushNotification bootstrap

Replace CrossFirebasePushNotification.Current with DI-injected IFirebasePushNotification

Update handlers to the new IPushNotificationHandler interface

Optional but recommended:

Use IFirebasePushTelemetry for events/errors

Use CT overloads for operations invoked from UI/viewmodels

Migrating from Raw Firebase SDK You can remove:

Custom FirebaseMessagingService glue you created

Manual UNUserNotificationCenter delegate code (if you used FCM swizzling)

Custom preferences storage for token/topics

And instead:

Use UseFirebasePushNotifications + IFirebasePushNotification

Implement IPushNotificationHandler to handle your app-level behavior

License πŸ“„ This project is licensed under the MIT License.

Saved page with message 'Updated Shaunebu.MAUI.FirebasePushNotifications (not published) ~' Showing filters 1 through 1

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-android35.0 is compatible.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-ios18.0 is compatible.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 663 12/1/2025
1.0.4 664 12/1/2025
1.0.3 179 11/26/2025
1.0.2 168 11/26/2025
1.0.1 176 11/26/2025
1.0.0 174 11/26/2025