Seamless Notifications Across Different Platforms

Ariyan AshfaqueAriyan Ashfaque
6 min read

I will show you how to set up Firebase push notifications for Android and iOS in an Ionic 8 and Angular 18 app using the Ionic Push Notifications Capacitor Plugin API, including Firebase configuration for multiple environments.

Key Steps:

  1. Firebase Configuration for Multiple Environments: You'll configure Firebase for development, QA, and production environments, ensuring that notifications work seamlessly across all stages of your application.

  2. Ionic Capacitor Plugin for Push Notifications: The Ionic Push Notifications Capacitor Plugin API will be used for handling push notifications on both Android and iOS platforms.

  3. Android/iOS Adjustments: Specific configurations are required for each platform to ensure notifications are received properly.

  4. Node.js Backend Integration with FCM: A Node.js backend will be used to send push notifications, using Firebase Cloud Messaging to deliver notifications to users across different environments.

Step 1: Firebase Configuration Files for multiple Environment Support

When you integrate Firebase into an app, Firebase provides you with two configuration files:

  • google-services.json for Android.

  • GoogleService-Info.plist for iOS.

In a multi-environment scenario (e.g., Dev, QA, and Prod), Firebase provides separate configuration files for each project. To manage this efficiently, I organized these files in dedicated folders within the Android and iOS directories.

Folder Structure

For Android:

android/
  ├── app/
  │   ├── google-services.json (placeholder)
  ├── beta/
  │   ├── google-services.json (QA)
  ├── debug/
  │   ├── google-services.json (Development)
  ├── release/
      ├── google-services.json (Production)

For iOS:

ios/
  ├── App/
  │   ├── App/
  │       ├── GoogleService-Info.plist (placeholder)
  ├── beta/
  │   ├── GoogleService-Info.plist (QA)
  ├── debug/
  │   ├── GoogleService-Info.plist (Development)
  ├── release/
      ├── GoogleService-Info.plist (Production)

Each folder contains a different configuration file that corresponds to its environment.

Step 2: Customizing angular.json for Multi-Environment Builds

To make the build process easier for different environments, I customized the angular.json file. This lets Angular use the right environment settings based on whether you're building for development, QA, or production.

"configurations": {
  "dev": {
    "sourceMap": true,
    "namedChunks": true,
    "vendorChunk": true,
    "optimization": false,
    "buildOptimizer": false,
    "extractLicenses": false
  },
  "qa": {
    "aot": true,
    "sourceMap": false,
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.qa.ts"
      }
    ]
  },
  "prod": {
    "aot": true,
    "sourceMap": false,
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.prod.ts"
      }
    ]
  }
}

This setup helps optimize your build configurations based on the environment. For example, dev configurations focus on faster builds, while prod ensures your build is highly optimized for performance.

Step 3: Build Commands for iOS and Android

To make switching between different Firebase projects easier during builds, I created custom commands in package.json. These commands automate copying the correct Firebase configuration file and syncing with Capacitor.

Here’s what I added to the package.json:

"scripts": {
  "build:ios:qa": "ionic build --qa && cp ios/App/App/beta/GoogleService-Info.plist ios/App/App/GoogleService-Info.plist && ionic cap sync ios",
  "build:ios:dev": "ionic build --dev && cp ios/App/App/debug/GoogleService-Info.plist ios/App/App/GoogleService-Info.plist && ionic cap sync ios",
  "build:android:qa": "ionic build --qa && cp android/app/src/beta/google-services.json android/app/google-services.json && ionic cap sync android",
  "build:android:dev": "ionic build --dev && cp android/app/src/debug/google-services.json android/app/google-services.json && ionic cap sync android",
  "build:ios:prod": "ionic build --prod && cp ios/App/App/release/GoogleService-Info.plist ios/App/App/GoogleService-Info.plist && ionic cap sync ios",
  "build:android:prod": "ionic build --prod && cp android/app/src/release/google-services.json android/app/google-services.json && ionic cap sync android"
}

Explanation of the Commands:

  • build:ios:qa: Builds the app for QA, copies the correct GoogleService-Info.plist from the beta folder, and syncs the changes to iOS using Capacitor.

  • build:android:prod: Builds the app for production, copies the correct google-services.json from the release folder, and syncs with Android.

This setup makes switching between environments seamless, saving you from manually replacing files each time you switch builds.

Android Setup

build.gradle (Android)

For Firebase push notifications on Android, the Firebase services plugin should be applied conditionally if google-services.json is present. This ensures the build won't fail when the file is missing, which is useful in multi-environment setups.

try {
    def servicesJSON = file('google-services.json')
    if (servicesJSON.text) {
        apply plugin: 'com.google.gms.google-services'
    }
} catch(Exception e) {
    logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}

This block ensures that Firebase services are applied only when google-services.json exists.

Android Manifest (AndroidManifest.xml)

In the AndroidManifest file, you need to configure a default notification icon and set internet permissions.

<application>
    <meta-data
        android:resource="@mipmap/ic_launcher" 
        android:name="com.google.firebase.messaging.default_notification_icon" />
</application>

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

The meta-data entry ensures a default icon is used for Firebase notifications, and the INTERNET permission is essential for Firebase messaging.

iOS Setup

Podfile Configuration

For iOS, Firebase Messaging needs to be added to the Podfile in order to enable push notifications.

target 'App' do
  capacitor_pods
  pod 'Firebase/Messaging'
end

Run pod install after modifying the Podfile to apply these changes.

AppDelegate.swift Modifications

Update the AppDelegate.swift file to set up Firebase and manage push notification registration.

import Firebase
import FirebaseMessaging

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure()
    return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Messaging.messaging().apnsToken = deviceToken
    Messaging.messaging().token(completion: { (token, error) in
        if let error = error {
            NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
        } else if let token = token {
            NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: token)
        }
    })
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}

These methods handle push notification registration and token retrieval for Firebase Messaging on iOS.

Frontend: Capacitor Push Notifications in Ionic

To register the device for push notifications and manage the token, use Capacitor’s PushNotifications API.

Registering for Notifications

In the frontend, you need to request permissions and register the device for push notifications. The device token is retrieved upon successful registration.

import { PushNotifications } from "@capacitor/push-notifications";

private RegisterNotifications = async () => {
    let permission = await PushNotifications.checkPermissions();

    if (permission.receive === "prompt") {
      permission = await PushNotifications.requestPermissions();
    }

    if (permission.receive !== "granted") {
      throw new Error("User denied permissions!");
    }
    await PushNotifications.register();
};

Listening for the Device Token

Once registered, listen for the token used to send notifications to this device:

private NotificationListeners = async () => {
    await PushNotifications.addListener("registration", (token) => {
      console.info("Registration token: ", token.value);
    });
};
  • On iOS, the token contains the APNS token.

  • On Android, the token contains the FCM token.

Backend: Sending Push Notifications

In the backend, I use Node.js with Firebase Cloud Functions to send push notifications. The FCM messaging.send() method is used to send notifications to registered devices.

Once you have the device token (retrieved from the frontend), you can pass it to this function to send a push notification to the targeted device.

Conclusion

With this setup, Firebase push notifications are integrated across both Android and iOS environments in your Ionic and Angular app. The project is organized to support multi-environment builds (Dev, QA, Prod), and the notification handling is managed in the backend using Firebase Cloud Functions.

Here’s a summary of what we've covered:

  1. Android Setup: Changes in build.gradle, AndroidManifest.xml, and Firebase configuration files for each environment.

  2. iOS Setup: Firebase Messaging in the Podfile and push notification handling in AppDelegate.swift.

  3. Frontend: Push notifications registration and token retrieval using Capacitor’s PushNotifications API.

  4. Backend: Sending notifications using Firebase Cloud Functions in Node.js.

With these steps, you should now have a fully functional Firebase push notification system for your mobile app.

0
Subscribe to my newsletter

Read articles from Ariyan Ashfaque directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ariyan Ashfaque
Ariyan Ashfaque