Implementing Push Notifications in .NET MAUI with Firebase Cloud Messaging


Today, in digital world, keeping user engaged is the key determinant of any application’s success. With push notifications, you have a direct to tell your users what’s going on in a timely fashion, with alerts and also personal messages. The rise of cross platform frameworks such as .NET Multi platform UI (.NET MAUI) is the fact that developers can build native applications across multiple platforms from a single codebase.

Firebase Cloud Messaging (FCM) is a wonderful service from Google that allows you to communicate with your users on various platforms. Leveraging the full capacity of push notifications from FCM, you can integrate FCM with .NET MAUI.

In this article I will explain how to implement push notifications in your .NET MAUI application using Firebase Cloud Messaging. In this article, let’s explore how to set your development environment, begin with FCM integration with your application and effectively handle notifications.

Understanding Firebase Cloud Messaging

Firebase Cloud Messaging is a free cross platform messaging service that allows you to reliably send notifications and messages to your users without it breaking. It supports many message types so you can have real time communication with your users’ app. Key features include:

Multi-Platform Support: It works great on Android, iOS, and web applications.
Message Targeting: You can create the messages to follow the specific devices, groups of devices or topics.
Analytics: It presents detailed delivery reports and analytics in order to measure message effectiveness.
Scalability: Created to process large amount of messages.
FCM takes care of delivering messages across multiple platforms and devices; It takes away all the complexity in message delivery. With message targeting, delivery analytics and device group messaging, it is a solid solution for the push notifications implementation.

Why Use Push Notifications?

User Engagement and Retention are imperative for user push notifications. They allow you to:

Deliver Timely Information: Intuitively, it will inform users about important events, updates or promotions instantly. Take for instance, a news app can send out breaking news alerts.
Increase User Engagement: Bring your users back with customized messages. One example is that a shopping app can tell users about their discount offers.
Enhance User Experience: Recommend value added services such as reminders, alerts and interactive messages. An example is a Calendar app that sends reminders for events.
Push notifications can help your app achieve higher effectiveness and satisfy your users better.

Setting Up a Firebase Project

  1. Create a New Project
    Access Firebase Console:
    Head to the Firebase Console.
    Add a Project:
    Click on Add project.
    Project Name: Then enter a unique name for your project.
    Analytics: Enable Or Disable Google Analytics for your project.
    Create Project: Click on Create Project to continue.
  2. Register Your App
    Add Android App:
    Click on the Android icon.
    Package Name: Give in your app’s’s package name (e.g. com.companyname.appname).
    App Nickname: Helpful for identification, optional.
    SHA-1 Certificate: Optional for FCM and required for some Firebase features.
    Register App: Proceed by clicking the Register App option.
    Add iOS App:
    Click on the iOS icon.
    Bundle ID: Paste your app’s bundle identifier.
    App Nickname: Optional.
    App Store ID: Optional.
    Register App: Click Register App.
  3. Download Configuration Files
    Android (google-services.json):
    Download the google-services.json file after registering the Android app.
    Put this file into your .NET MAUI project’s Platforms/Android location.
    iOS (GoogleService-Info.plist):
    Then, after registering the iOS app, download the GoogleService-Info.plist file.
    Simply place this file in the Platforms/iOS project in Visual Studio.
    You can also set its Build Action to BundleResource.
  4. Add Firebase SDKs
    For Android:
    In the integration section, we’ll include the Firebase SDKs via NuGet packages.
    For iOS:
    As we’ll also do the same thing we’ll also add the required Firebase SDKs using NuGet packages.

Completing these steps will take your Firebase project and make it ready to conversation with your .NET MAUI application.

Integrating Firebase Cloud Messaging with .NET MAUI

Create a new .NET MAUI project or adjust your project like below.

Add Firebase Packages

Install Xamarin.Firebase.Messaging for Android:
Click on the Package Manager Console in Visual Studio menu.
Run the following command:

    Install-Package Xamarin.Firebase.Messaging -Version 123.0.4

    Change 123.0.4 here with the most current version at your disposal.

    Install Firebase.CloudMessaging for iOS:

    Run the following command:

    Install-Package Firebase.CloudMessaging -Version 4.8.0

    Replace 4.8.0 with the latest version available.

    Include Configuration Files

    Android:
    Then put the google-services.json in Platforms/Android folder.
    In Visual Studio right click on the google-services.json file and change its Build Action to GoogleServicesJson.
    iOS:
    Then, add the GoogleService-Info.plist to Platforms/iOS project.
    You can also set BundleResource as Build Action.

      Configuring the App for Firebase

      Update AndroidManifest.xml:
      Navigate to Platforms/Android on AndroidManifest.xml.
      Add the following permissions inside the tag:

      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

      Add the Firebase Messaging service inside the <application> tag:

      <application ...>
        <service
            android:name="com.google.firebase.messaging.FirebaseMessagingService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
      </application>

      Add Google Services Gradle Plugin:

      Create a file named build.gradle in the Platforms/Android directory with the following content:

      buildscript {
          repositories {
              google()
              mavenCentral()
          }
          dependencies {
              classpath 'com.google.gms:google-services:4.3.10'
          }
      }

      Modify the build.gradle file to apply the Google Services plugin:

      apply plugin: 'com.android.application'
      apply plugin: 'com.google.gms.google-services'

      iOS Configuration
      Enable Push Notifications Capability:
      Open Platforms/iOS/Entitlements.plist.
      To enable Push Notifications go ahead and add a key aps-environment in the value of development or production in relation to your environment.
      Register for Remote Notifications in AppDelegate.cs:

      using Firebase.Core;
      using UserNotifications;
      
      public override bool FinishedLaunching(UIApplication app, NSDictionary options)
      {
          // Configure Firebase
          App.Configure();
      
          // Request permission to display alerts and play sounds.
          UNUserNotificationCenter.Current.RequestAuthorization(
              UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
              (granted, error) => {
                  if (granted)
                  {
                      InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications);
                  }
              });
      
          return base.FinishedLaunching(app, options);
      }
      
      // Handle token refresh
      public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
      {
          // Convert device token to string
          var token = deviceToken.ToString();
      
          // Send token to Firebase
          Messaging.SharedInstance.ApnsToken = deviceToken;
      }

      Implement UNUserNotificationCenterDelegate:

      In your AppDelegate add the IUNUserNotificationCenterDelegate interface.
      Handle incoming notifications through the implemention of the delegate methods.

      After you complete the integration steps the process to use Firebase Cloud Messaging with your .NET MAUI app is complete.

      Implementing Push Notifications in .NET MAUI

      Firebase is now integrated so now you can implement the functionality to receive and handle push notifications.

      Handling Device Registration

      Obtain FCM Token

      Android:

      Create a service class that extends FirebaseMessagingService:

      [Service]
      [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
      public class MyFirebaseMessagingService : FirebaseMessagingService
      {
          public override void OnNewToken(string token)
          {
              base.OnNewToken(token);
              // TODO: Send token to your server or save it as needed
              Console.WriteLine($"FCM Token: {token}");
          }
      }

      iOS:

      In AppDelegate.cs, implement the MessagingDelegate:

      using Firebase.CloudMessaging;
      
      public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IMessagingDelegate
      {
          public override bool FinishedLaunching(UIApplication app, NSDictionary options)
          {
              // Existing code...
              Messaging.SharedInstance.Delegate = this;
              return base.FinishedLaunching(app, options);
          }
      
          [Export("messaging:didReceiveRegistrationToken:")]
          public void DidReceiveRegistrationToken(Messaging messaging, string token)
          {
              // TODO: Send token to your server or save it as needed
              Console.WriteLine($"FCM Token: {token}");
          }
      }

      Store or Send Token to Server

      Send Token to Server:

      As a responsive of this activate token, create a method that can send the token to your server via HTTP POST or anything else you prefer.

      private async Task SendTokenToServer(string token)
      {
          var client = new HttpClient();
          var content = new StringContent($"{{\"token\":\"{token}\"}}", Encoding.UTF8, "application/json");
          await client.PostAsync("https://yourserver.com/api/register-token", content);
      }

      Store Token Locally

      Keep the token in use secure storage mechanism

      await SecureStorage.SetAsync("fcm_token", token);

      Monitor Token Refresh

      Android:
      In MyFirebaseMessagingService is where you can put your token refresh.
      iOS:
      So the method for handling token refresh in AppDelegate is the DidReceiveRegistrationToken method.

      Sending Notifications from the Firebase Console

      1. Click to Firebase Console.
        Head over to your Firebase project, and from the side menu pick Cloud Messaging.
      2. Compose a New Notification
        Click on “Send your first message”:
        Click on “New notification” if you’ve already sent messages.
        Enter Notification Details:
        Title: e.g., “Welcome to Our App!”
        Text: For example, they could say “We thank you for installing our app, stay tuned for updates.”
      3. Target Your App
        Select App:
        Under Target choose your registered app.
        Target Specific Devices (Optional):
        By Topic, User Segment or Device Group you can focus your targeting.
      4. Send the Message
        Schedule Delivery:
        For now or a future date and time.
        Send:
        Select ‘Review’ then ‘Publish’ and the notification will be sent.

      It allows you to test the notification reception on your app without setting up a server.

      Handling Received Notifications in the App

      Create a Custom Messaging Service

      Android:

      Continue using MyFirebaseMessagingService

      public override void OnMessageReceived(RemoteMessage message)
      {
          base.OnMessageReceived(message);
          // TODO: Handle the message
          SendNotification(message.GetNotification().Title, message.GetNotification().Body);
      }
      
      private void SendNotification(string title, string messageBody)
      {
          var notificationBuilder = new NotificationCompat.Builder(this, "default")
              .SetSmallIcon(Resource.Drawable.ic_stat_ic_notification)
              .SetContentTitle(title)
              .SetContentText(messageBody)
              .SetAutoCancel(true);
      
          var notificationManager = NotificationManagerCompat.From(this);
          notificationManager.Notify(0, notificationBuilder.Build());
      }

      Add Notification Channel for Android Oreo and Above:

      private void CreateNotificationChannel()
      {
          if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
          {
              var channel = new NotificationChannel("default", "Default Channel", NotificationImportance.Default)
              {
                  Description = "Firebase Notifications"
              };
              var notificationManager = (NotificationManager)GetSystemService(NotificationService);
              notificationManager.CreateNotificationChannel(channel);
          }
      }

      iOS:

      Implement methods in UNUserNotificationCenterDelegate like below:

      [Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
      public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
      {
          // Show the notification even if the app is in the foreground
          completionHandler(UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Sound);
      }
      
      [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
      public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
      {
          // TODO: Handle notification tap action
          var userInfo = response.Notification.Request.Content.UserInfo;
          // Process userInfo as needed
          completionHandler();
      }

      Handle Notification Clicks

      Android

      Launch an activity when you tap that notification by defining a pending intent that leads to the activity.

      var intent = new Intent(this, typeof(MainActivity));
      intent.AddFlags(ActivityFlags.ClearTop);
      var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
      
      notificationBuilder.SetContentIntent(pendingIntent);

      iOS

      Steps to Handle Notification Clicks in iOS:

      • In your AppDelegate class implement the IUNUserNotificationCenterDelegate interface.
      • And then, set your AppDelegate instance to the UNUserNotificationCenter.Current.Delegate.
      • In order to handle the notification click event, we override the DidReceiveNotificationResponse method.
      • If you want to extract custom data from the notification payload do so.
      • Allows you to navigate to specific pages in your app.
      [Register("AppDelegate")]
          public class AppDelegate : MauiUIApplicationDelegate, IUNUserNotificationCenterDelegate, IMessagingDelegate
          {
              public override bool FinishedLaunching(UIApplication app, NSDictionary options)
              {
                  // Configure Firebase
                  App.Configure();
      
                  // Set Messaging Delegate
                  Messaging.SharedInstance.Delegate = this;
      
                  // Request permission to display notifications
                  UNUserNotificationCenter.Current.RequestAuthorization(
                      UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
                      (granted, error) =>
                      {
                          if (granted)
                          {
                              // Set the notification center delegate
                              UNUserNotificationCenter.Current.Delegate = this;
      
                              // Register for remote notifications
                              InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications);
                          }
                          else
                          {
                              Console.WriteLine("Permission not granted: " + error?.LocalizedDescription);
                          }
                      });
      
                  return base.FinishedLaunching(app, options);
              }
      
              // Called when a notification is delivered to a foreground app.
              [Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
              public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
              {
                  // Show the notification even if the app is in the foreground
                  completionHandler(UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Badge);
              }
      
              // Called when the user interacts with the notification (e.g., taps on it).
              [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
              public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
              {
                  // Handle the notification action
                  var userInfo = response.Notification.Request.Content.UserInfo;
      
                  // Extract custom data from the notification payload
                  if (userInfo != null && userInfo.ContainsKey(new NSString("customKey")))
                  {
                      var customValue = userInfo[new NSString("customKey")].ToString();
                      // TODO: Navigate to a specific page or perform an action using customValue
                      HandleNotificationAction(customValue);
                  }
                  else
                  {
                      // TODO: Handle the default action
                      HandleNotificationAction(null);
                  }
      
                  // Must be called when finished
                  completionHandler();
              }
      
              // Handle FCM token refresh
              [Export("messaging:didReceiveRegistrationToken:")]
              public void DidReceiveRegistrationToken(Messaging messaging, string fcmToken)
              {
                  Console.WriteLine($"FCM Token: {fcmToken}");
                  // TODO: If necessary send token to your server
              }
      
              // Handle successful registration for remote notifications
              public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
              {
                  Messaging.SharedInstance.ApnsToken = deviceToken;
              }
      
              // Handle failure to register for remote notifications
              public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
              {
                  Console.WriteLine($"Failed to register for remote notifications: {error.LocalizedDescription}");
              }
      
              private void HandleNotificationAction(string customValue)
              {
                  // Ensure this is run on the main thread
                  Device.BeginInvokeOnMainThread(() =>
                  {
                      // Access the current application
                      var app = Application.Current;
      
                      // Navigate to a specific page based on customValue
                      if (app.MainPage is NavigationPage navigationPage)
                      {
                          if (!string.IsNullOrEmpty(customValue))
                          {
                              // Navigate to the page with custom data
                              navigationPage.PushAsync(new NotificationDetailPage(customValue));
                          }
                          else
                          {
                              // Navigate to a default page
                              navigationPage.PushAsync(new MainPage());
                          }
                      }
                      else
                      {
                          // If MainPage is not a NavigationPage, set it
                          app.MainPage = new NavigationPage(new MainPage());
                      }
                  });
              }
          }

      Notification Payload Customization

      Notification Messages vs. Data Messages
      Data Messages:
      They contain custom key value pairs defined by the developer.
      Even when the app is in the foreground, it’s handled by the app.
      Notification Messages:
      It contains predefined keys such as title and body.
      If the app is not on foreground, is displayed automatically on the system tray.

        Crafting Custom Payloads

        Data message example

        {
          "to": "<FCM_TOKEN>",
          "data": {
            "title": "New Feature!",
            "body": "Check out the latest update in our app.",
            "url": "https://yourapp.com/feature"
          }
        }

        Sending via HTTP POST Request

        Endpoint: https://fcm.googleapis.com/fcm/sendHeaders:

        • Content-Type: application/json
        • Authorization: key=<SERVER_KEY>

        Create a request using tools like Postman or write server side script to send the request.

        Handling in the App

        Android

        In OnMessageReceived method, access data payload:

        if (message.Data.Count > 0)
        {
            var title = message.Data["title"];
            var body = message.Data["body"];
            var url = message.Data["url"];
            // Display notification or update UI
        }

        iOS

        In DidReceiveRemoteNotification method, access data:

        public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
        {
            if (userInfo != null)
            {
                var data = userInfo["data"] as NSDictionary;
                var title = data["title"].ToString();
                var body = data["body"].ToString();
                var url = data["url"].ToString();
                // Handle data
            }
        }

        Handling Background Notifications

        Background Processing

        Android

        • The app receives data messages even when it’s in the background.
        • Make sure your FirebaseMessagingService is setup correctly to recieve background messages.

        iOS

        • To allow processing of data messages in the background, it has special requirements.
        • Enable Remote notifications background mode:
        • Go into your project’s Capabilities, select Background Modes, and Remote notifications.

        iOS Specifics

        Set content-available Flag

        Include “content-available”: Add a 1 in your payload to indicate a silent notification.

        {
          "to": "<FCM_TOKEN>",
          "content_available": true,
          "priority": "high",
          "data": {
            "title": "Background Update",
            "body": "Data has been updated.",
            "key": "value"
          }
        }

        Implement DidReceiveRemoteNotification

        In AppDelegate.cs

        public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
        {
            // Process data message
            completionHandler(UIBackgroundFetchResult.NewData);
        }

        Using Server-Side APIs

        Alternative: Using Third-Party Libraries

        If you want to use a library that provides more abstraction of this complexity, then you’ll be using third party NuGet packages such as FirebaseAdmin (unofficial). Install it using:

        Install-Package FirebaseAdmin

        Example

        using FirebaseAdmin;
        using FirebaseAdmin.Messaging;
        using Google.Apis.Auth.OAuth2;
        
        public async Task SendNotificationWithFirebaseAdminAsync(string fcmToken)
        {
            if (FirebaseApp.DefaultInstance == null)
            {
                FirebaseApp.Create(new AppOptions()
                {
                    Credential = GoogleCredential.FromFile("path/to/serviceAccountKey.json"),
                });
            }
        
            var message = new Message()
            {
                Token = fcmToken,
                Notification = new Notification()
                {
                    Title = "Hello from FirebaseAdmin",
                    Body = "This is a test notification."
                }
            };
        
            string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
            Console.WriteLine("Successfully sent message: " + response);
        }

        Sending Notifications from C# Using Firebase Cloud Messaging

        Installing Required NuGet Packages

        Install-Package Google.Apis.Auth
        Install-Package Google.Apis.FirebaseCloudMessaging.v1
        Install-Package Newtonsoft.Json
        using Google.Apis.Auth.OAuth2;
        using Newtonsoft.Json;
        using System;
        using System.IO;
        using System.Net.Http;
        using System.Text;
        using System.Threading.Tasks;
        
        public class FcmSender
        {
            private async Task<string> GetAccessTokenAsync()
            {
                string[] scopes = new string[] { "https://www.googleapis.com/auth/firebase.messaging" };
                GoogleCredential credential;
        
                using (var stream = new FileStream("path/to/serviceAccountKey.json", FileMode.Open, FileAccess.Read))
                {
                    credential = GoogleCredential.FromStream(stream)
                        .CreateScoped(scopes);
                }
        
                var accessToken = await credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
                return accessToken;
            }
        
            public async Task SendPushNotificationAsync(string fcmToken)
            {
                string accessToken = await GetAccessTokenAsync();
        
                var message = new
                {
                    message = new
                    {
                        token = fcmToken,
                        notification = new
                        {
                            title = "Hello from C#",
                            body = "This is a push notification sent from a C# backend."
                        }
                        // You can include "data" payload here if needed
                    }
                };
        
                string jsonMessage = JsonConvert.SerializeObject(message);
        
                using (var httpClient = new HttpClient())
                {
                    httpClient.DefaultRequestHeaders.Authorization =
                        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
        
                    var request = new HttpRequestMessage(HttpMethod.Post,
                        "https://fcm.googleapis.com/v1/projects/your-project-id/messages:send");
                    request.Content = new StringContent(jsonMessage, Encoding.UTF8, "application/json");
        
                    var response = await httpClient.SendAsync(request);
        
                    if (response.IsSuccessStatusCode)
                    {
                        Console.WriteLine("Notification sent successfully.");
                    }
                    else
                    {
                        string responseBody = await response.Content.ReadAsStringAsync();
                        Console.WriteLine($"Error sending notification: {response.StatusCode}\n{responseBody}");
                    }
                }
            }
        }

        Sending http request

        string userFcmToken = "user_device_fcm_token";
        await SendPushNotificationAsync(userFcmToken);

        Common Issues and Solutions

        Token Retrieval Failures:

        • Make sure that the network is working, and Firebase can be initialized.
        • In token retrieval methods check for exceptions.


        Notifications Not Received:

        • Make sure that you app have the proper permission in it.
        • We check to see if the device is registered and the token if valid.


        Payload Format Errors:

        • Use online or stand alone JSON payload validate tools.
        • Inputs can be formatted correctly to keys and values.

        Conclusion

        Push notifications integrations using Firebase Cloud Messaging in your .NET MAUI application increase user engagement and offer real time communication. Using this article as guidance, you’ll set up your development environment, integrate FCM with your app, and add notification handling.

        Mastering these concepts empowers you deliver timely, and personalized content to your users, thereby enhancing the user experience. Second, if working with Firebase, you may want to dive deeper on their advanced features like topic messaging, conditional targeting, and analytics to localize communication strategies in your app.

        ,