Tutorial: Send push notifications to React Native apps using Azure Notification Hubs via a backend service

Download Sample Download the sample

In this tutorial, you use Azure Notification Hubs to push notifications to a React Native application targeting Android and iOS.

An ASP.NET Core Web API backend is used to handle device registration for the client using the latest and best Installation approach. The service will also send push notifications in a cross-platform manner.

These operations are handled using the Notification Hubs SDK for backend operations. Further detail on the overall approach is provided in the Registering from your app backend documentation.

This tutorial takes you through the following steps:

Prerequisites

To follow along, you require:

  • An Azure subscription where you can create and manage resources.
  • A Mac with Visual Studio for Mac installed (or a PC running Visual Studio 2019 with the Mobile Development with .NET workload).
  • The ability to run the app on either Android (physical or emulator devices) or iOS (physical devices only).

For Android, you must have:

  • A developer unlocked physical device or an emulator (running API 26 and above with Google Play Services installed).

For iOS, you must have:

Note

The iOS Simulator does not support remote notifications and so a physical device is required when exploring this sample on iOS. However, you do not need to run the app on both Android and iOS in order to complete this tutorial.

You can follow the steps in this first-principles example with no prior experience. However, you'll benefit from having familiarity with the following aspects.

The steps provided are for Visual Studio for Mac and Visual Studio Code but it's possible to follow along using Visual Studio 2019.

Set up Push Notification Services and Azure Notification Hub

In this section, you set up Firebase Cloud Messaging (FCM) and Apple Push Notification Services (APNS). You then create and configure a notification hub to work with those services.

Create a Firebase project and enable Firebase Cloud Messaging for Android

  1. Sign in to the Firebase console. Create a new Firebase project entering PushDemo as the Project name.

    Note

    A unique name will be generated for you. By default this is comprised of a lowercase variant of the name you provided plus a generated number separated by a dash. You can change this if you want provided it is still globally unique.

  2. After you create your project, select Add Firebase to your Android app.

    Add Firebase to your Android app

  3. On the Add Firebase to your Android app page, take the following steps.

    1. For the Android package name, enter a name for your package. For example: com.<organization_identifier>.<package_name>.

      Specify the package name

    2. Select Register app.

    3. Select Download google-services.json. Then save the file into a local folder for use later on and select Next.

      Download google-services.json

    4. Select Next.

    5. Select Continue to console

      Note

      If the Continue to console button is not enabled, due to the verify installation check, then choose Skip this step.

  4. In the Firebase console, select the cog for your project. Then select Project Settings.

    Select Project Settings

    Note

    If you haven't downloaded the google-services.json file, you can do download it on this page.

  5. Switch to the Cloud Messaging tab at the top. Copy and save the Server key for later use. You use this value to configure your notification hub.

    Copy server key

Register your iOS app for push notifications

To send push notifications to an iOS app, register your application with Apple, and also register for push notifications.

  1. If you haven't already registered your app, browse to the iOS Provisioning Portal at the Apple Developer Center. Sign in to the portal with your Apple ID, navigate to Certificates, Identifiers & Profiles, then select Identifiers. Click + to register a new app.

    iOS Provisioning Portal App IDs page

  2. On the Register a New Identifier screen, select the App IDs radio button. Then select Continue.

    iOS Provisioning Portal register new ID page

  3. Update the following three values for your new app, and then select Continue:

    • Description: Type a descriptive name for your app.

    • Bundle ID: Enter a Bundle ID of the form com.<organization_identifier>.<product_name> as mentioned in the App Distribution Guide. In the following screenshot, the mobcat value is used as an organization identifier and the PushDemo value is used as the product name.

      iOS Provisioning Portal register app ID page

    • Push Notifications: Check the Push Notifications option in the Capabilities section.

      Form to register a new App ID

      This action generates your App ID and requests that you confirm the information. Select Continue, then select Register to confirm the new App ID.

      Confirm new App ID

      After you select Register, you see the new App ID as a line item in the Certificates, Identifiers & Profiles page.

  4. In the Certificates, Identifiers & Profiles page, under Identifiers, locate the App ID line item that you created. Then, select its row to display the Edit your App ID Configuration screen.

Creating a certificate for Notification Hubs

A certificate is required to enable the notification hub to work with Apple Push Notification Services (APNS) and can be provided in one of two ways:

  1. Creating a p12 push certificate that can be uploaded directly to Notification Hub (the original approach)

  2. Creating a p8 certificate that can be used for token-based authentication (the newer and recommended approach)

The newer approach has a number of benefits as documented in Token-based (HTTP/2) authentication for APNS. Fewer steps are required but is also mandated for specific scenarios. However, steps have been provided for both approaches since either will work for the purposes of this tutorial.

OPTION 1: Creating a p12 push certificate that can be uploaded directly to Notification Hub
  1. On your Mac, run the Keychain Access tool. It can be opened from the Utilities folder or the Other folder on the Launchpad.

  2. Select Keychain Access, expand Certificate Assistant, and then select Request a Certificate from a Certificate Authority.

    Use Keychain Access to request a new certificate

    Note

    By default, Keychain Access selects the first item in the list. This can be a problem if you're in the Certificates category and Apple Worldwide Developer Relations Certification Authority is not the first item in the list. Make sure you have a non-key item, or the Apple Worldwide Developer Relations Certification Authority key is selected, before generating the CSR (Certificate Signing Request).

  3. Select your User Email Address, enter your Common Name value, make sure that you specify Saved to disk, and then select Continue. Leave CA Email Address blank as it isn't required.

    Expected certificate information

  4. Enter a name for the Certificate Signing Request (CSR) file in Save As, select the location in Where, and then select Save.

    Choose a file name for the certificate

    This action saves the CSR file in the selected location. The default location is Desktop. Remember the location chosen for the file.

  5. Back on the Certificates, Identifiers & Profiles page in the iOS Provisioning Portal, scroll down to the checked Push Notifications option, and then select Configure to create the certificate.

    Edit App ID page

  6. The Apple Push Notification service TLS/SSL Certificates window appears. Select the Create Certificate button under the Development TLS/SSL Certificate section.

    Create certificate for App ID button

    The Create a new Certificate screen is displayed.

    Note

    This tutorial uses a development certificate. The same process is used when registering a production certificate. Just make sure that you use the same certificate type when sending notifications.

  7. Select Choose File, browse to the location where you saved the CSR file, and then double-click the certificate name to load it. Then select Continue.

  8. After the portal creates the certificate, select the Download button. Save the certificate, and remember the location to which it's saved.

    Generated certificate download page

    The certificate is downloaded and saved to your computer in your Downloads folder.

    Locate certificate file in the Downloads folder

    Note

    By default, the downloaded development certificate is named aps_development.cer.

  9. Double-click the downloaded push certificate aps_development.cer. This action installs the new certificate in the Keychain, as shown in the following image:

    Keychain access certificates list showing new certificate

    Note

    Although the name in your certificate might be different, the name will be prefixed with Apple Development iOS Push Services and have the appropriate bundle identifier associated with it.

  10. In Keychain Access, Control + Click on the new push certificate that you created in the Certificates category. Select Export, name the file, select the p12 format, and then select Save.

    Export certificate as p12 format

    You can choose to protect the certificate with a password, but a password is optional. Click OK if you want to bypass password creation. Make a note of the file name and location of the exported p12 certificate. They're used to enable authentication with APNs.

    Note

    Your p12 file name and location might be different than what is pictured in this tutorial.

OPTION 2: Creating a p8 certificate that can be used for token-based authentication
  1. Make note of the following details:

    • App ID Prefix (Team ID)
    • Bundle ID
  2. Back in Certificates, Identifiers & Profiles, click Keys.

    Note

    If you already have a key configured for APNS, you can re-use the p8 certificate that you downloaded right after it was created. If so, you can ignore steps 3 through 5.

  3. Click the + button (or the Create a key button) to create a new key.

  4. Provide a suitable Key Name value, then check the Apple Push Notifications service (APNS) option, and then click Continue, followed by Register on the next screen.

  5. Click Download and then move the p8 file (prefixed with AuthKey_) to a secure local directory, then click Done.

    Note

    Be sure to keep your p8 file in a secure place (and save a backup). After downloading your key, it cannot be re-downloaded as the server copy is removed.

  6. On Keys, click on the key that you created (or an existing key if you have chosen to use that instead).

  7. Make note of the Key ID value.

  8. Open your p8 certificate in a suitable application of your choice such as Visual Studio Code. Make note of the key value (between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----).

    -----BEGIN PRIVATE KEY-----
    <key_value>
    -----END PRIVATE KEY-----

    Note

    This is the token value that will be used later to configure Notification Hub.

At the end of these steps, you should have the following information for use later in Configure your notification hub with APNS information:

  • Team ID (see step 1)
  • Bundle ID (see step 1)
  • Key ID (see step 7)
  • Token value (p8 key value obtained in step 8)

Create a provisioning profile for the app

  1. Return to the iOS Provisioning Portal, select Certificates, Identifiers & Profiles, select Profiles from the left menu, and then select + to create a new profile. The Register a New Provisioning Profile screen appears.

  2. Select iOS App Development under Development as the provisioning profile type, and then select Continue.

    Provisioning profile list

  3. Next, select the app ID you created from the App ID drop-down list, and select Continue.

    Select the App ID

  4. In the Select certificates window, select the development certificate that you use for code signing, and select Continue.

    Note

    This certificate is not the push certificate you created in the previous step. This is your development certificate. If one does not exist, you must create it since this is a prerequisite for this tutorial. Developer certificates can be created in the Apple Developer Portal, via Xcode or in Visual Studio.

  5. Return to the Certificates, Identifiers & Profiles page, select Profiles from the left menu, and then select + to create a new profile. The Register a New Provisioning Profile screen appears.

  6. In the Select certificates window, select the development certificate that you created. Then select Continue.

  7. Next, select the devices to use for testing, and select Continue.

  8. Finally, choose a name for the profile in Provisioning Profile Name, and select Generate.

    Choose a provisioning profile name

  9. When the new provisioning profile is created, select Download. Remember the location to which it's saved.

  10. Browse to the location of the provisioning profile, and then double-click it to install it on your development machine.

Create a Notification Hub

In this section, you create a notification hub and configure authentication with APNS. You can use a p12 push certificate or token-based authentication. If you want to use a notification hub that you've already created, you can skip to step 5.

  1. Sign in to Azure.

  2. Click Create a resource, then search for and choose Notification Hub, then click Create.

  3. Update the following fields, then click Create:

    BASIC DETAILS

    Subscription: Choose the target Subscription from the drop-down list
    Resource Group: Create a new Resource Group (or pick an existing one)

    NAMESPACE DETAILS

    Notification Hub Namespace: Enter a globally unique name for the Notification Hub namespace

    Note

    Ensure the Create new option is selected for this field.

    NOTIFICATION HUB DETAILS

    Notification Hub: Enter a name for the Notification Hub
    Location: Choose a suitable location from the drop-down list
    Pricing Tier: Keep the default Free option

    Note

    Unless you have reached the maximum number of hubs on the free tier.

  4. Once the Notification Hub has been provisioned, navigate to that resource.

  5. Navigate to your new Notification Hub.

  6. Select Access Policies from the list (under MANAGE).

  7. Make note of the Policy Name values along with their corresponding Connection String values.

Configure your Notification Hub with APNS information

Under Notification Services, select Apple then follow the appropriate steps based on the approach you chose previously in the Creating a Certificate for Notification Hubs section.

Note

Use the Production for Application Mode only if you want to send push notifications to users who purchased your app from the store.

OPTION 1: Using a .p12 push certificate

  1. Select Certificate.

  2. Select the file icon.

  3. Select the .p12 file that you exported earlier, and then select Open.

  4. If necessary, specify the correct password.

  5. Select Sandbox mode.

  6. Select Save.

OPTION 2: Using token-based authentication

  1. Select Token.

  2. Enter the following values that you acquired earlier:

    • Key ID
    • Bundle ID
    • Team ID
    • Token
  3. Choose Sandbox.

  4. Select Save.

Configure your notification hub with FCM information

  1. Select Google (GCM/FCM) in the Settings section on the left menu.
  2. Enter the server key you noted down from the Google Firebase Console.
  3. Select Save on the toolbar.

Create an ASP.NET Core Web API backend application

In this section, you create the ASP.NET Core Web API backend to handle device registration and the sending of notifications to the React Native mobile app.

Create a web project

  1. In Visual Studio, select File > New Solution.

  2. Select .NET Core > App > ASP.NET Core > API > Next.

  3. In the Configure your new ASP.NET Core Web API dialog, select Target Framework of .NET Core 3.1.

  4. Enter PushDemoApi for the Project Name and then select Create.

  5. Start debugging (Command + Enter) to test the templated app.

    Note

    The templated app is configured to use the WeatherForecastController as the launchUrl. This is set in Properties > launchSettings.json.

    If you are prompted with an Invalid development certificate found message:

    1. Click Yes to agree to running the 'dotnet dev-certs https' tool to fix this. The 'dotnet dev-certs https' tool then prompt you to enter a password for the certificate and the password for your Keychain.

    2. Click Yes when prompted to Install and trust the new certificate, then enter the password for your Keychain.

  6. Expand the Controllers folder, then delete WeatherForecastController.cs.

  7. Delete WeatherForecast.cs.

  8. Set up local configuration values using the Secret Manager tool. Decoupling the secrets from the solution ensures that they don't end up in source control. Open Terminal then go to the directory of the project file and run the following commands:

    dotnet user-secrets init
    dotnet user-secrets set "NotificationHub:Name" <value>
    dotnet user-secrets set "NotificationHub:ConnectionString" <value>
    

    Replace the placeholder values with your own notification hub name and connection string values. You made a note of them in the create a notification hub section. Otherwise, you can look them up in Azure.

    NotificationsHub:Name:
    See Name in the Essentials summary at the top of Overview.

    NotificationHub:ConnectionString:
    See DefaultFullSharedAccessSignature in Access Policies

    Note

    For production scenarios, you can look at options such as Azure KeyVault to securely store the connection string. For simplicity, the secrets will be added to the Azure App Service application settings.

Authenticate clients using an API Key (Optional)

API keys aren't as secure as tokens, but will suffice for the purposes of this tutorial. An API key can be configured easily via the ASP.NET Middleware.

  1. Add the API key to the local configuration values.

    dotnet user-secrets set "Authentication:ApiKey" <value>
    

    Note

    You should replace the placeholder value with your own and make a note of it.

  2. Control + Click on the PushDemoApi project, choose New Folder from the Add menu, then click Add using Authentication as the Folder Name.

  3. Control + Click on the Authentication folder, then choose New File... from the Add menu.

  4. Select General > Empty Class, enter ApiKeyAuthOptions.cs for the Name, then click New adding the following implementation.

    using Microsoft.AspNetCore.Authentication;
    
    namespace PushDemoApi.Authentication
    {
        public class ApiKeyAuthOptions : AuthenticationSchemeOptions
        {
            public const string DefaultScheme = "ApiKey";
            public string Scheme => DefaultScheme;
            public string ApiKey { get; set; }
        }
    }
    
  5. Add another Empty Class to the Authentication folder called ApiKeyAuthHandler.cs, then add the following implementation.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Text.Encodings.Web;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    
    namespace PushDemoApi.Authentication
    {
        public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOptions>
        {
            const string ApiKeyIdentifier = "apikey";
    
            public ApiKeyAuthHandler(
                IOptionsMonitor<ApiKeyAuthOptions> options,
                ILoggerFactory logger,
                UrlEncoder encoder,
                ISystemClock clock)
                : base(options, logger, encoder, clock) {}
    
            protected override Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                string key = string.Empty;
    
                if (Request.Headers[ApiKeyIdentifier].Any())
                {
                    key = Request.Headers[ApiKeyIdentifier].FirstOrDefault();
                }
                else if (Request.Query.ContainsKey(ApiKeyIdentifier))
                {
                    if (Request.Query.TryGetValue(ApiKeyIdentifier, out var queryKey))
                        key = queryKey;
                }
    
                if (string.IsNullOrWhiteSpace(key))
                    return Task.FromResult(AuthenticateResult.Fail("No api key provided"));
    
                if (!string.Equals(key, Options.ApiKey, StringComparison.Ordinal))
                    return Task.FromResult(AuthenticateResult.Fail("Invalid api key."));
    
                var identities = new List<ClaimsIdentity> {
                    new ClaimsIdentity("ApiKeyIdentity")
                };
    
                var ticket = new AuthenticationTicket(
                    new ClaimsPrincipal(identities), Options.Scheme);
    
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
        }
    }
    

    Note

    An Authentication Handler is a type that implements the behavior of a scheme, in this case a custom API Key scheme.

  6. Add another Empty Class to the Authentication folder called ApiKeyAuthenticationBuilderExtensions.cs, then add the following implementation.

    using System;
    using Microsoft.AspNetCore.Authentication;
    
    namespace PushDemoApi.Authentication
    {
        public static class AuthenticationBuilderExtensions
        {
            public static AuthenticationBuilder AddApiKeyAuth(
                this AuthenticationBuilder builder,
                Action<ApiKeyAuthOptions> configureOptions)
            {
                return builder
                    .AddScheme<ApiKeyAuthOptions, ApiKeyAuthHandler>(
                        ApiKeyAuthOptions.DefaultScheme,
                        configureOptions);
            }
        }
    }
    

    Note

    This extension method simplifies the middleware configuration code in Startup.cs making it more readable and generally easier to follow.

  7. In Startup.cs, update the ConfigureServices method to configure the API Key authentication below the call to the services.AddControllers method.

    using PushDemoApi.Authentication;
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme;
            options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme;
        }).AddApiKeyAuth(Configuration.GetSection("Authentication").Bind);
    }
    
  8. Still in Startup.cs, update the Configure method to call the UseAuthentication and UseAuthorization extension methods on the app's IApplicationBuilder. Ensure those methods are called after UseRouting and before app.UseEndpoints.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseRouting();
    
        app.UseAuthentication();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    

    Note

    Calling UseAuthentication registers the middleware which uses the previously registered authentication schemes (from ConfigureServices). This must be called before any middleware that depends on users being authenticated.

Add dependencies and configure services

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

Use of the notification hub and the Notification Hubs SDK for backend operations is encapsulated within a service. The service is registered and made available through a suitable abstraction.

  1. Control + Click on the Dependencies folder, then choose Manage NuGet Packages....

  2. Search for Microsoft.Azure.NotificationHubs and ensure it's checked.

  3. Click Add Packages, then click Accept when prompted to accept the license terms.

  4. Control + Click on the PushDemoApi project, choose New Folder from the Add menu, then click Add using Models as the Folder Name.

  5. Control + Click on the Models folder, then choose New File... from the Add menu.

  6. Select General > Empty Class, enter PushTemplates.cs for the Name, then click New adding the following implementation.

    namespace PushDemoApi.Models
    {
        public class PushTemplates
        {
            public class Generic
            {
                public const string Android = "{ \"notification\": { \"title\" : \"PushDemo\", \"body\" : \"$(alertMessage)\"}, \"data\" : { \"action\" : \"$(alertAction)\" } }";
                public const string iOS = "{ \"aps\" : {\"alert\" : \"$(alertMessage)\"}, \"action\" : \"$(alertAction)\" }";
            }
    
            public class Silent
            {
                public const string Android = "{ \"data\" : {\"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\"} }";
                public const string iOS = "{ \"aps\" : {\"content-available\" : 1, \"apns-priority\": 5, \"sound\" : \"\", \"badge\" : 0}, \"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\" }";
            }
        }
    }
    

    Note

    This class contains the tokenized notification payloads for the generic and silent notifications required by this scenario. The payloads are defined outside of the Installation to allow experimentation without having to update existing installations via the service. Handling changes to installations in this way is out of scope for this tutorial. For production, consider custom templates.

  7. Add another Empty Class to the Models folder called DeviceInstallation.cs, then add the following implementation.

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace PushDemoApi.Models
    {
        public class DeviceInstallation
        {
            [Required]
            public string InstallationId { get; set; }
    
            [Required]
            public string Platform { get; set; }
    
            [Required]
            public string PushChannel { get; set; }
    
            public IList<string> Tags { get; set; } = Array.Empty<string>();
        }
    }
    
  8. Add another Empty Class to the Models folder called NotificationRequest.cs, then add the following implementation.

    using System;
    
    namespace PushDemoApi.Models
    {
        public class NotificationRequest
        {
            public string Text { get; set; }
            public string Action { get; set; }
            public string[] Tags { get; set; } = Array.Empty<string>();
            public bool Silent { get; set; }
        }
    }
    
  9. Add another Empty Class to the Models folder called NotificationHubOptions.cs, then add the following implementation.

    using System.ComponentModel.DataAnnotations;
    
    namespace PushDemoApi.Models
    {
        public class NotificationHubOptions
        {
            [Required]
            public string Name { get; set; }
    
            [Required]
            public string ConnectionString { get; set; }
        }
    }
    
  10. Add a new folder to the PushDemoApi project called Services.

  11. Add an Empty Interface to the Services folder called INotificationService.cs, then add the following implementation.

    using System.Threading;
    using System.Threading.Tasks;
    using PushDemoApi.Models;
    
    namespace PushDemoApi.Services
    {
        public interface INotificationService
        {
            Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token);
            Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token);
            Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token);
        }
    }
    
  12. Add an Empty Class to the Services folder called NotificationHubsService.cs, then add the following code to implement the INotificationService interface:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using PushDemoApi.Models;
    
    namespace PushDemoApi.Services
    {
        public class NotificationHubService : INotificationService
        {
            readonly NotificationHubClient _hub;
            readonly Dictionary<string, NotificationPlatform> _installationPlatform;
            readonly ILogger<NotificationHubService> _logger;
    
            public NotificationHubService(IOptions<NotificationHubOptions> options, ILogger<NotificationHubService> logger)
            {
                _logger = logger;
                _hub = NotificationHubClient.CreateClientFromConnectionString(
                    options.Value.ConnectionString,
                    options.Value.Name);
    
                _installationPlatform = new Dictionary<string, NotificationPlatform>
                {
                    { nameof(NotificationPlatform.Apns).ToLower(), NotificationPlatform.Apns },
                    { nameof(NotificationPlatform.Fcm).ToLower(), NotificationPlatform.Fcm }
                };
            }
    
            public async Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token)
            {
                if (string.IsNullOrWhiteSpace(deviceInstallation?.InstallationId) ||
                    string.IsNullOrWhiteSpace(deviceInstallation?.Platform) ||
                    string.IsNullOrWhiteSpace(deviceInstallation?.PushChannel))
                    return false;
    
                var installation = new Installation()
                {
                    InstallationId = deviceInstallation.InstallationId,
                    PushChannel = deviceInstallation.PushChannel,
                    Tags = deviceInstallation.Tags
                };
    
                if (_installationPlatform.TryGetValue(deviceInstallation.Platform, out var platform))
                    installation.Platform = platform;
                else
                    return false;
    
                try
                {
                    await _hub.CreateOrUpdateInstallationAsync(installation, token);
                }
                catch
                {
                    return false;
                }
    
                return true;
            }
    
            public async Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token)
            {
                if (string.IsNullOrWhiteSpace(installationId))
                    return false;
    
                try
                {
                    await _hub.DeleteInstallationAsync(installationId, token);
                }
                catch
                {
                    return false;
                }
    
                return true;
            }
    
            public async Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token)
            {
                if ((notificationRequest.Silent &&
                    string.IsNullOrWhiteSpace(notificationRequest?.Action)) ||
                    (!notificationRequest.Silent &&
                    (string.IsNullOrWhiteSpace(notificationRequest?.Text)) ||
                    string.IsNullOrWhiteSpace(notificationRequest?.Action)))
                    return false;
    
                var androidPushTemplate = notificationRequest.Silent ?
                    PushTemplates.Silent.Android :
                    PushTemplates.Generic.Android;
    
                var iOSPushTemplate = notificationRequest.Silent ?
                    PushTemplates.Silent.iOS :
                    PushTemplates.Generic.iOS;
    
                var androidPayload = PrepareNotificationPayload(
                    androidPushTemplate,
                    notificationRequest.Text,
                    notificationRequest.Action);
    
                var iOSPayload = PrepareNotificationPayload(
                    iOSPushTemplate,
                    notificationRequest.Text,
                    notificationRequest.Action);
    
                try
                {
                    if (notificationRequest.Tags.Length == 0)
                    {
                        // This will broadcast to all users registered in the notification hub
                        await SendPlatformNotificationsAsync(androidPayload, iOSPayload, token);
                    }
                    else if (notificationRequest.Tags.Length <= 20)
                    {
                        await SendPlatformNotificationsAsync(androidPayload, iOSPayload, notificationRequest.Tags, token);
                    }
                    else
                    {
                        var notificationTasks = notificationRequest.Tags
                            .Select((value, index) => (value, index))
                            .GroupBy(g => g.index / 20, i => i.value)
                            .Select(tags => SendPlatformNotificationsAsync(androidPayload, iOSPayload, tags, token));
    
                        await Task.WhenAll(notificationTasks);
                    }
    
                    return true;
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "Unexpected error sending notification");
                    return false;
                }
            }
    
            string PrepareNotificationPayload(string template, string text, string action) => template
                .Replace("$(alertMessage)", text, StringComparison.InvariantCulture)
                .Replace("$(alertAction)", action, StringComparison.InvariantCulture);
    
            Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, CancellationToken token)
            {
                var sendTasks = new Task[]
                {
                    _hub.SendFcmNativeNotificationAsync(androidPayload, token),
                    _hub.SendAppleNativeNotificationAsync(iOSPayload, token)
                };
    
                return Task.WhenAll(sendTasks);
            }
    
            Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, IEnumerable<string> tags, CancellationToken token)
            {
                var sendTasks = new Task[]
                {
                    _hub.SendFcmNativeNotificationAsync(androidPayload, tags, token),
                    _hub.SendAppleNativeNotificationAsync(iOSPayload, tags, token)
                };
    
                return Task.WhenAll(sendTasks);
            }
        }
    }
    

    Note

    The tag expression provided to SendTemplateNotificationAsync is limited to 20 tags. It is limited to 6 for most operators but the expression contains only ORs (||) in this case. If there are more than 20 tags in the request then they must be split into multiple requests. See the Routing and Tag Expressions documentation for more detail.

  13. In Startup.cs, update the ConfigureServices method to add the NotificationHubsService as a singleton implementation of INotificationService.

    
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddSingleton<INotificationService, NotificationHubService>();
    
        services.AddOptions<NotificationHubOptions>()
            .Configure(Configuration.GetSection("NotificationHub").Bind)
            .ValidateDataAnnotations();
    }
    

Create the notifications API

  1. Control + Click on the Controllers folder, then choose New File... from the Add menu.

  2. Select ASP.NET Core > Web API Controller Class, enter NotificationsController for the Name, then click New.

    Note

    If you're following with Visual Studio 2019, choose the API Controller with read/write actions template.

  3. Add the following namespaces to the top of the file.

    using System.ComponentModel.DataAnnotations;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
  4. Update the templated controller so it derives from ControllerBase and is decorated with the ApiController attribute.

    [ApiController]
    [Route("api/[controller]")]
    public class NotificationsController : ControllerBase
    {
        // Templated methods here
    }
    

    Note

    The Controller base class provides support support for views but this is not needed in this case and so ControllerBase can be used instead. If you're following with Visual Studio 2019, you can skip this step.

  5. If you chose to complete the Authenticate clients using an API Key section, you should decorate the NotificationsController with the Authorize attribute as well.

    [Authorize]
    
  6. Update the constructor to accept the registered instance of INotificationService as an argument and assign it to a readonly member.

    readonly INotificationService _notificationService;
    
    public NotificationsController(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }
    
  7. In launchSettings.json (within the Properties folder), change the launchUrl from weatherforecast to api/notifications to match the URL specified in the RegistrationsController Route attribute.

  8. Start debugging (Command + Enter) to validate the app is working with the new NotificationsController and returns a 401 Unauthorized status.

    Note

    Visual Studio may not automatically launch the app in the browser. You will use Postman to test the API from this point on.

  9. On a new Postman tab, set the request to GET. Enter the address below replacing the placeholder <applicationUrl> with the https applicationUrl found in Properties > launchSettings.json.

    <applicationUrl>/api/notifications
    

    Note

    The applicationUrl should be 'https://localhost:5001' for the default profile. If you're using IIS (default in Visual Studio 2019 on Windows), you should use the applicationUrl specified in the iisSettings item instead. You will receive a 404 response if the address is incorrect.

  10. If you chose to complete the Authenticate clients using an API Key section, be sure to configure the request headers to include your apikey value.

    Key Value
    apikey <your_api_key>
  11. Click the Send button.

    Note

    You should receive a 200 OK status with some JSON content.

    If you receive an SSL certificate verification warning, you can switch the request SSL certificate verification Postman setting off in the Settings.

  12. Replace the templated class methods in NotificationsController.cs with the following code.

    [HttpPut]
    [Route("installations")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<IActionResult> UpdateInstallation(
        [Required]DeviceInstallation deviceInstallation)
    {
        var success = await _notificationService
            .CreateOrUpdateInstallationAsync(deviceInstallation, HttpContext.RequestAborted);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    
    [HttpDelete()]
    [Route("installations/{installationId}")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<ActionResult> DeleteInstallation(
        [Required][FromRoute]string installationId)
    {
        var success = await _notificationService
            .DeleteInstallationByIdAsync(installationId, CancellationToken.None);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    
    [HttpPost]
    [Route("requests")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<IActionResult> RequestPush(
        [Required]NotificationRequest notificationRequest)
    {
        if ((notificationRequest.Silent &&
            string.IsNullOrWhiteSpace(notificationRequest?.Action)) ||
            (!notificationRequest.Silent &&
            string.IsNullOrWhiteSpace(notificationRequest?.Text)))
            return new BadRequestResult();
    
        var success = await _notificationService
            .RequestNotificationAsync(notificationRequest, HttpContext.RequestAborted);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    

Create the API app

You now create an API App in Azure App Service for hosting the backend service.

  1. Sign in to the Azure portal.

  2. Click Create a resource, then search for and choose API App, then click Create.

  3. Update the following fields, then click Create.

    App name:
    Enter a globally unique name for the API App

    Subscription:
    Choose the same target Subscription you created the notification hub in.

    Resource Group:
    Choose the same Resource Group you created the notification hub in.

    App Service Plan/Location:
    Create a new App Service Plan

    Note

    Change from the default option to a plan that includes SSL support. Otherwise, you will need to take the appropriate steps when working with the mobile app to prevent http requests from getting blocked.

    Application Insights:
    Keep the suggested option (a new resource will be created using that name) or pick an existing resource.

  4. Once the API App has been provisioned, navigate to that resource.

  5. Make note of the URL property in the Essentials summary at the top of the Overview. This URL is your backend endpoint that will be used later in this tutorial.

    Note

    The URL uses the API app name that you specified earlier, with the format https://<app_name>.azurewebsites.net.

  6. Select Configuration from the list (under Settings).

  7. For each of the settings below, click New application setting to enter the Name and a Value, then click OK.

    Name Value
    Authentication:ApiKey <api_key_value>
    NotificationHub:Name <hub_name_value>
    NotificationHub:ConnectionString <hub_connection_string_value>

    Note

    These are the same settings you defined previously in the user settings. You should be able to copy these over. The Authentication:ApiKey setting is required only if you chose to to complete the Authenticate clients using an API Key section. For production scenarios, you can look at options such as Azure KeyVault. These have been added as application settings for simplicity in this case.

  8. Once all application settings have been added click Save, then Continue.

Publish the backend service

Next, you deploy the app to the API App to make it accessible from all devices.

Note

The following steps are specific to Visual Studio for Mac. If you're following with Visual Studio 2019 on Windows, the publishing flow will be different. See Publish to Azure App Service on Windows.

  1. Change your configuration from Debug to Release if you haven't already done so.

  2. Control + Click the PushDemoApi project, and then choose Publish to Azure... from the Publish menu.

  3. Follow the auth flow if prompted to do so. Use the account that you used in the previous create the API App section.

  4. Select the Azure App Service API App you created previously from the list as your publish target, and then click Publish.

After you've completed the wizard, it publishes the app to Azure and then opens the app. Make a note of the URL if you haven't done so already. This URL is your backend endpoint that is used later in this tutorial.

Validating the published API

  1. In Postman open a new tab, set the request to PUT and enter the address below. Replace the placeholder with the base address you made note of in the previous publish the backend service section.

    https://<app_name>.azurewebsites.net/api/notifications/installations
    

    Note

    The base address should be in the format https://<app_name>.azurewebsites.net/

  2. If you chose to complete the Authenticate clients using an API Key section, be sure to configure the request headers to include your apikey value.

    Key Value
    apikey <your_api_key>
  3. Choose the raw option for the Body, then choose JSON from the list of format options, and then include some placeholder JSON content:

    {}
    
  4. Click Send.

    Note

    You should receive a 422 UnprocessableEntity status from the service.

  5. Do steps 1-4 again but this time specifying the requests endpoint to validate you receive a 400 Bad Request response.

    https://<app_name>.azurewebsites.net/api/notifications/requests
    

Note

It is not yet possible to test the API using valid request data since this will require platform-specific information from the client mobile app.

Create a cross-platform React Native application

In this section, you build a React Native mobile application implementing push notifications in a cross-platform manner.

It enables you to register and deregister from a notification hub via the backend service that you created.

An alert is displayed when an action is specified and the app is in the foreground. Otherwise, notifications appear in notification center.

Note

You would typically perform the registration (and deregistration) actions during the appropriate point in the application lifecycle (or as part of your first-run experience perhaps) without explicit user register/deregister inputs. However, this example will require explicit user input to allow this functionality to be explored and tested more easily.

Create the React Native solution

  1. In Terminal, update your environment tools, required to work with React Native using the following commands:

    # install node
    brew install node
    # or update
    brew update node
    # install watchman
    brew install watchman
    # or update
    brew upgrade watchman
    # install cocoapods
    sudo gem install cocoapods
    
  2. In Terminal, run the following command, if you have React Native CLI installed to uninstall it. Use npx to automatically access the latest React Native CLI version available:

    npm uninstall -g react-native-cli
    

    Note

    React Native has a built-in command line interface. Rather than install and manage a specific version of the CLI globally, we recommend you access the current version at runtime using npx, which ships with Node.js. With npx react-native <command>, the current stable version of the CLI will be downloaded and executed at the time the command is run.

  3. Navigate to your projects folder where you want to create the new application. Use the Typescript-based template by specifying the --template parameter:

    # init new project with npx
    npx react-native init PushDemo --template react-native-template-typescript
    
  4. Run metro server, which builds JavaScript bundles and monitors any code updates to refresh the bundles in real time:

    cd PushDemo
    npx react-native start
    
  5. Run the iOS app to verify the setup. Make sure you started an iOS simulator or connected an iOS device, before executing the following command:

    npx react-native run-ios
    
  6. Run the Android app to verify the setup. It requires a few additional steps to configure an Android emulator or device to be able accessing the React Native metro server. The following commands generate initial JavaScript bundle for Android and put it into the assets folder.

    # create assets folder for the bundle
    mkdir android/app/scr/main/assets
    # build the bundle
    npx react-native bundle --platform android --dev true --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
    # enable ability for sim to access the localhost
    adb reverse tcp:8081 tcp:8081
    

    This script will be pre-deployed with the initial version of the app. Once deployed, configure your emulator or device to access the metro server by specifying the server ip address. Execute the following command to build and run the Android application:

    npx react-native run-android
    

    Once in the app, hit CMD+M (emulator) or shake the device to populate the developer settings, navigate to Settings > Change Bundle Location, and specify the metro server ip address with the default port: <metro-server-ip-address>:8081.

  7. In the App.tsx file, apply any change to the page layout, save it and make the change is automatically reflected in both iOS and Android apps.

    Note

    Detailed development environment setup guide is available in the official documentation

Install required packages

You need the following three packages for this sample to work:

  1. React Native Push Notifications iOS - Project GitHub

    This package was created when the PushNotificationIOS was split out from the core of React Native. The package natively implements push notifications for iOS and provides React Native interface to access it. Run the following command to install the package:

    yarn add @react-native-community/push-notification-ios
    
  2. React Native Push Notifications Cross-platform

    This package implements local and remote notifications on iOS and Android in a cross-platform way. Run the following command to install the package:

    yarn add react-native-push-notification
    
  3. Device info package The package provides information about a device in runtime. Use it to define a device identifier, which is used to register for push notification. Run the following command to install the package:

    yarn add react-native-device-info
    

Implement the cross-platform components

  1. Create and implement DemoNotificationHandler:

    import PushNotification from 'react-native-push-notification';
    
    class DemoNotificationHandler {
      private _onRegister: any;
      private _onNotification: any;
    
      onNotification(notification: any) {
        console.log('NotificationHandler:', notification);
    
        if (typeof this._onNotification === 'function') {
          this._onNotification(notification);
        }
      }
    
      onRegister(token: any) {
        console.log('NotificationHandler:', token);
    
        if (typeof this._onRegister === 'function') {
          this._onRegister(token);
        }
      }
    
      attachTokenReceived(handler: any) {
        this._onRegister = handler;
      }
    
      attachNotificationReceived(handler: any) {
        this._onNotification = handler;
      }
    }
    
    const handler = new DemoNotificationHandler();
    
    PushNotification.configure({
      onRegister: handler.onRegister.bind(handler),
      onNotification: handler.onNotification.bind(handler),
      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },
      popInitialNotification: true,
      requestPermissions: true,
    });
    
    export default handler;
    
  2. Create and implement DemoNotificationService:

    import PushNotification from 'react-native-push-notification';
    import DemoNotificationHandler from './DemoNotificationHandler';
    
    export default class DemoNotificationService {
      constructor(onTokenReceived: any, onNotificationReceived: any) {
        DemoNotificationHandler.attachTokenReceived(onTokenReceived);
        DemoNotificationHandler.attachNotificationReceived(onNotificationReceived);
        PushNotification.getApplicationIconBadgeNumber(function(number: number) {
          if(number > 0) {
            PushNotification.setApplicationIconBadgeNumber(0);
          }
        });
      }
    
      checkPermissions(cbk: any) {
        return PushNotification.checkPermissions(cbk);
      }
    
      requestPermissions() {
        return PushNotification.requestPermissions();
      }
    
      cancelNotifications() {
        PushNotification.cancelLocalNotifications();
      }
    
      cancelAll() {
        PushNotification.cancelAllLocalNotifications();
      }
    
      abandonPermissions() {
        PushNotification.abandonPermissions();
      }
    }
    
  3. Create and implement DemoNotificationRegistrationService:

    export default class DemoNotificationService {
        constructor(
            readonly apiUrl: string,
            readonly apiKey: string) {
        }
    
    async registerAsync(request: any): Promise<Response> {
            const method = 'PUT';
            const registerApiUrl = `${this.apiUrl}/notifications/installations`;
            const result = await fetch(registerApiUrl, {
                method: method,
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                    'apiKey': this.apiKey
                },
                body: JSON.stringify(request)
            });
    
            this.validateResponse(registerApiUrl, method, request, result);
            return result;
        }
    
        async deregisterAsync(deviceId: string): Promise<Response> {
            const method = 'DELETE';
            const deregisterApiUrl = `${this.apiUrl}/notifications/installations/${deviceId}`;
            const result = await fetch(deregisterApiUrl, {
                method: method,
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                    'apiKey': this.apiKey
                }
            });
    
            this.validateResponse(deregisterApiUrl, method, null, result);
            return result;
        }
    
        private validateResponse(requestUrl: string, method: string, requestPayload: any, response: Response) {
            console.log(`Request: ${method} ${requestUrl} => ${JSON.stringify(requestPayload)}\nResponse: ${response.status}`);
            if (!response || response.status != 200) {
                throw `HTTP error ${response.status}: ${response.statusText}`;
            }
        }
    }
    
  4. Configure the app. Open package.json and add the following script definition:

    "configure": "cp .app.config.tsx src/config/AppConfig.tsx"
    

    Then execute this script, which will copy the default config to the config folder.

    yarn configure
    

    Final step is to update the configuration file copied at the previous step with the API access information. Specify apiKey and apiUrl parameters:

    module.exports = {
        appName: "PushDemo",
        env: "production",
        apiUrl: "https://<azure-push-notifications-api-url>/api/",
        apiKey: "<api-auth-key>",
    };
    

Implement the cross-platform UI

  1. Define page layout

    <View style={styles.container}>
      {this.state.isBusy &&
        <ActivityIndicator></ActivityIndicator>
      }
      <View style={styles.button}>
        <Button title="Register" onPress={this.onRegisterButtonPress.bind(this)} disabled={this.state.isBusy} />
      </View>
      <View style={styles.button}>
        <Button title="Deregister" onPress={this.onDeregisterButtonPress.bind(this)} disabled={this.state.isBusy} />
      </View>
    </View>
    
  2. Apply styles

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: "center",
        justifyContent: 'flex-end',
        margin: 50,
      },
      button: {
        margin: 5,
        width: "100%",
      }
    });
    
  3. Initialize the page component

      state: IState;
      notificationService: DemoNotificationService;
      notificationRegistrationService: DemoNotificationRegistrationService;
      deviceId: string;
    
      constructor(props: any) {
        super(props);
        this.deviceId = DeviceInfo.getUniqueId();
        this.state = {
          status: "Push notifications registration status is unknown",
          registeredOS: "",
          registeredToken: "",
          isRegistered: false,
          isBusy: false,
        };
    
        this.notificationService = new DemoNotificationService(
          this.onTokenReceived.bind(this),
          this.onNotificationReceived.bind(this),
        );
    
        this.notificationRegistrationService = new DemoNotificationRegistrationService(
          Config.apiUrl,
          Config.apiKey,
        );
      }
    
  4. Define button click handlers

      async onRegisterButtonPress() {
        if (!this.state.registeredToken || !this.state.registeredOS) {
          Alert.alert("The push notifications token wasn't received.");
          return;
        }
    
        let status: string = "Registering...";
        let isRegistered = this.state.isRegistered;
        try {
          this.setState({ isBusy: true, status });
          const pnPlatform = this.state.registeredOS == "ios" ? "apns" : "fcm";
          const pnToken = this.state.registeredToken;
          const request = {
            installationId: this.deviceId,
            platform: pnPlatform,
            pushChannel: pnToken,
            tags: []
          };
          const response = await this.notificationRegistrationService.registerAsync(request);
          status = `Registered for ${this.state.registeredOS} push notifications`;
          isRegistered = true;
        } catch (e) {
          status = `Registration failed: ${e}`;
        }
        finally {
          this.setState({ isBusy: false, status, isRegistered });
        }
      }
    
      async onDeregisterButtonPress() {
        if (!this.notificationService)
          return;
    
        let status: string = "Deregistering...";
        let isRegistered = this.state.isRegistered;
        try {
          this.setState({ isBusy: true, status });
          await this.notificationRegistrationService.deregisterAsync(this.deviceId);
          status = "Deregistered from push notifications";
          isRegistered = false;
        } catch (e) {
          status = `Deregistration failed: ${e}`;
        }
        finally {
          this.setState({ isBusy: false, status, isRegistered });
        }
      }
    
  5. Handle received token registrations and push notifications

      onTokenReceived(token: any) {
        console.log(`Received a notification token on ${token.os}`);
        this.setState({ registeredToken: token.token, registeredOS: token.os, status: `The push notifications token has been received.` });
    
        if (this.state.isRegistered && this.state.registeredToken && this.state.registeredOS) {
          this.onRegisterButtonPress();
        }
      }
    
      onNotificationReceived(notification: any) {
        console.log(`Received a push notification on ${this.state.registeredOS}`);
        this.setState({ status: `Received a push notification...` });
    
        if (notification.data.message) {
          Alert.alert(AppConfig.appName, `${notification.data.action} action received`);
        }
      }
    };
    

Configure the native Android project for push notifications

Configure required Android packages

The package is automatically linked when building the app. You have a few additional steps below to complete the configuration process.

Configure Android manifest

In your "android/app/src/main/AndroidManifest.xml", verify the package name, permissions, and required services. Make sure that you registered RNPushNotificationPublisher and RNPushNotificationBootEventReceiver receivers, and registered the RNPushNotificationListenerService service. The notifications metadata can be used to customize your push notifications appearance.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="YOUR_PACKAGE_NAME">

      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      <uses-permission android:name="android.permission.VIBRATE" />
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

      <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme">

        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
                    android:value="PushDemo Channel"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_description"
                    android:value="PushDemo Channel Description"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_foreground"
                    android:value="true"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color"
                    android:resource="@android:color/white"/>

        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <activity
          android:name=".MainActivity"
          android:label="@string/app_name"
          android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
          android:launchMode="singleTask"
          android:windowSoftInputMode="adjustResize">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
      </application>

</manifest>

Configure Google services

In "android/app/build.gradle" register Google Services:

dependencies {
  ...
  implementation 'com.google.firebase:firebase-analytics:17.3.0'
  ...
}

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

Copy the "google-services.json" file that you downloaded during FCM setup to the project folder "android/app/".

Handle push notifications for Android

You configured the existing RNPushNotificationListenerService service to handle incoming Android push notifications. This service was registered earlier in the application manifest. It processes incoming notifications and proxies them to the cross-platform React Native part. No additional steps are required.

Configure the native iOS project for push notifications

Configure required iOS packages

The package is automatically linked when building the app. All you need to do is to install the native pods:

npx pod-install

Configure Info.plist and Entitlements.plist

  1. Go into your "PushDemo/ios" folder and open "PushDemo.xcworkspace" workspace, select the top project "PushDemo" and select the "Signing & Capabilities" tab.

  2. Update Bundle Identifier to match the value used in the provisioning profile.

  3. Add two new Capabilities using the - "+" button:

    • Background Mode capability and tick Remote Notifications.
    • Push Notifications capability

Handle push notifications for iOS

  1. Open "AppDelegate.h" and add the following import:

    #import <UserNotifications/UNUserNotificationCenter.h>
    
  2. Update list of protocols, supported by the "AppDelegate", by adding UNUserNotificationCenterDelegate:

    @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
    
  3. Open "AppDelegate.m" and configure all the required iOS callbacks:

    #import <UserNotifications/UserNotifications.h>
    #import <RNCPushNotificationIOS.h>
    
    ...
    
    // Required to register for notifications
    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
    {
     [RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
    }
    
    // Required for the register event.
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
     [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
    }
    
    // Required for the notification event. You must call the completion handler after handling the remote notification.
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
    {
      [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
    }
    
    // Required for the registrationError event.
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
    {
     [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
    }
    
    // IOS 10+ Required for localNotification event
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
    didReceiveNotificationResponse:(UNNotificationResponse *)response
             withCompletionHandler:(void (^)(void))completionHandler
    {
      [RNCPushNotificationIOS didReceiveNotificationResponse:response];
      completionHandler();
    }
    
    // IOS 4-10 Required for the localNotification event.
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
    {
     [RNCPushNotificationIOS didReceiveLocalNotification:notification];
    }
    
    //Called when a notification is delivered to a foreground app.
    -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
    {
      completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
    }
    

Test the solution

You can now test sending notifications via the backend service.

Send a test notification

  1. Open a new tab in Postman.

  2. Set the request to POST, and enter the following address:

    https://<app_name>.azurewebsites.net/api/notifications/requests
    
  3. If you chose to complete the Authenticate clients using an API Key section, be sure to configure the request headers to include your apikey value.

    Key Value
    apikey <your_api_key>
  4. Choose the raw option for the Body, then choose JSON from the list of format options, and then include some placeholder JSON content:

    {
        "text": "Message from Postman!",
        "action": "action_a"
    }
    
  5. Select the Code button, which is under the Save button on the upper right of the window. The request should look similar to the following example when displayed for HTML (depending on whether you included an apikey header):

    POST /api/notifications/requests HTTP/1.1
    Host: https://<app_name>.azurewebsites.net
    apikey: <your_api_key>
    Content-Type: application/json
    
    {
        "text": "Message from backend service",
        "action": "action_a"
    }
    
  6. Run the PushDemo application on one or both of the target platforms (Android and iOS).

    Note

    If you are testing on Android ensure that you are not running in Debug, or if the app has been deployed by running the application then force close the app and start it again from the launcher.

  7. In the PushDemo app, tap on the Register button.

  8. Back in Postman, close the Generate Code Snippets window (if you haven't done so already) then click the Send button.

  9. Validate that you get a 200 OK response in Postman and the alert appears in the app showing ActionA action received.

  10. Close the PushDemo app, then click the Send button again in Postman.

  11. Validate that you get a 200 OK response in Postman again. Validate that a notification appears in the notification area for the PushDemo app with the correct message.

  12. Tap on the notification to confirm that it opens the app and displayed the ActionA action received alert.

  13. Back in Postman, modify the previous request body to send a silent notification specifying action_b instead of action_a for the action value.

    {
        "action": "action_b",
        "silent": true
    }
    
  14. With the app still open, click the Send button in Postman.

  15. Validate that you get a 200 OK response in Postman and that the alert appears in the app showing ActionB action received instead of ActionA action received.

  16. Close the PushDemo app, then click the Send button again in Postman.

  17. Validate that you get a 200 OK response in Postman and that the silent notification doesn't appear in the notification area.

Troubleshooting

No response from the backend service

When testing locally, ensure that the backend service is running and is using the correct port.

If testing against the Azure API App, check the service is running and has been deployed and has started without error.

Be sure to check you've specified the base address correctly in Postman or in the mobile app configuration when testing via the client. The base address should indicatively be https://<api_name>.azurewebsites.net/ or https://localhost:5001/ when testing locally.

Not receiving notifications on Android after starting or stopping a debug session

Ensure you register again after starting or stopping a debug session. The debugger will cause a new Firebase token to be generated. The notification hub installation must be updated as well.

Receiving a 401 status code from the backend service

Validate that you're setting the apikey request header and this value matches the one you had configured for the backend service.

If you receive this error when testing locally, ensure the key value you defined in the client config, matches the Authentication:ApiKey user-setting value used by the API.

If you're testing with an API App, ensure the key value in the client config file matches the Authentication:ApiKey application setting you're using in the API App.

Note

If you had created or changed this setting after you had deployed the backend service then you must restart the service in order for it take effect.

If you chose not to complete the Authenticate clients using an API Key section, ensure that you didn't apply the Authorize attribute to the NotificationsController class.

Receiving a 404 status code from the backend service

Validate that the endpoint and HTTP request method is correct. For example, the endpoints should indicatively be:

  • [PUT] https://<api_name>.azurewebsites.net/api/notifications/installations
  • [DELETE] https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
  • [POST] https://<api_name>.azurewebsites.net/api/notifications/requests

Or when testing locally:

  • [PUT] https://localhost:5001/api/notifications/installations
  • [DELETE] https://localhost:5001/api/notifications/installations/<installation_id>
  • [POST] https://localhost:5001/api/notifications/requests

When specifying the base address in the client app, ensure it ends with a /. The base address should indicatively be https://<api_name>.azurewebsites.net/ or https://localhost:5001/ when testing locally.

Unable to register and a notification hub error message is displayed

Verify that the test device has network connectivity. Then, determine the Http response status code by setting a breakpoint to inspect the StatusCode property value in the HttpResponse.

Review the previous troubleshooting suggestions where applicable based on the status code.

Set a breakpoint on the lines that return these specific status codes for the respective API. Then try calling the backend service when debugging locally.

Validate the backend service is working as expected via Postman using the appropriate payload. Use the actual payload created by the client code for the platform in question.

Review the platform-specific configuration sections to ensure that no steps have been missed. Check that suitable values are being resolved for installation id and token variables for the appropriate platform.

Unable to resolve an ID for the device error message is displayed

Review the platform-specific configuration sections to ensure that no steps have been missed.

Next steps

You should now have a basic React Native app connected to a notification hub via a backend service and can send and receive notifications.

You'll likely need to adapt the example used in this tutorial to fit your own scenario. Implementing more robust error handling, retry logic, and logging is also recommended.

Visual Studio App Center can be quickly incorporated into mobile apps providing analytics and diagnostics to aid in troubleshooting.