Hello friends! Today, I’m going to tell you about a Swift file I frequently use to send notifications in my mobile apps: FCMManager.swift. I wrote this file while working with Firebase Cloud Messaging (FCM), and I’ll explain how it simplifies sending notifications to both a single device and multiple devices. With this file, you’ll be able to design a panel and send notifications directly without needing to go to the Firebase console

What Does FCMManager Do?

FCMManager is a helper class I created to make sending notifications with Firebase more practical. I wrote it as a singleton, meaning there’s only one instance of it in my app, and I can easily access it from anywhere. So, what does this class do?

  • FCM Token Retrieval: I fetch the FCM token of the device running the app.
  • Single Device Notification: I send a notification like “Hey, want to ask something?” to a specific device.
  • Multiple Device Notification: I send a bulk notification to a group of users at once.
  • JWT Authentication: I generate the access token required to use Firebase’s server APIs.

Our Dependencies

  • FirebaseMessaging: My go-to for retrieving FCM tokens and sending notifications.
  • SwiftJWT: Makes my life so much easier when generating Firebase’s authentication token (JWT).

SwiftJWT:

  • URL: https://github.com/Kitura/Swift-JWT.git
  • How to Add?: Go to File > Add Packages in Xcode, paste this URL, select the latest version (e.g., 3.6.0 or higher), and add it.
  • Details: A simple and effective library for creating JWTs.

I won’t cover the Firebase setup here.

Key Information We’ll Use

In the file, I use some critical pieces of information, and it’s vital to get them from the right place. These include:

  • serviceAccountEmail: The email address of the Firebase service account.
  • privateKey: The private key of the service account, a long text in PEM format.
  • projectId: The Firebase project’s ID.

Where Do I Find These?
We get this info from the Firebase console. Let me walk you through it:

Go to Firebase Console: Visit console.firebase.google.com and open your project.

Create a Service Account:

  • Click the gear icon in the top left and go to Project Settings.
  • Switch to the Service Accounts tab.
  • You’ll see “Firebase Admin SDK” with a Generate new private key button next to it. Click that.
  • A JSON file will download, containing info like private_key, client_email, and project_id.

Extract the Info:

  • serviceAccountEmail: The client_email value from the JSON.
  • privateKey: The private_key value from the JSON, a long text starting with — — -BEGIN PRIVATE KEY — — -.
  • projectId: The project_id value from the JSON.

Security Warning: It’s critical not to hardcode this info into your code. Instead, pull it from a secure location, like a Config.plist file. Why? If this info falls into the wrong hands, your project could be at risk.

Diving Into the Code

1. FirebaseClaims Struct

struct FirebaseClaims: Claims {
let iss: String
let scope: String
let aud: String
let iat: Int
let exp: Int
}

This is a structure I created to obtain Firebase’s OAuth 2.0 token. I define the JWT claims here:

  • iss: The service account email (serviceAccountEmail), saying “I’m sending this token.”
  • scope: Requesting access to Firebase Messaging (https://www.googleapis.com/auth/firebase.messaging).
  • aud: Who the token is for, i.e., the URL where the token request is sent.
  • iat: The token’s creation time (current time in seconds).
  • exp: The token’s expiration time (I set it to 1 hour from now).
    This struct guides me when creating a token with SwiftJWT.

2. FCMManager Class

class FCMManager {
static let shared = FCMManager()
private let fcmEndpoint = "https://fcm.googleapis.com/v1/projects/projectId/messages:send"
private let serviceAccountEmail = "service account email"
private let privateKey = """
[YOUR PRIVATE KEY GOES HERE - PULLED FROM THE JSON FILE FROM FIREBASE CONSOLE]
"""
private let projectId = "projectId"
private init() {}
}

This is the core of my class. The singleton structure lets me access it easily from anywhere. I used private init() to prevent external instantiation.

  • fcmEndpoint: The URL for sending notifications to Firebase. I dynamically insert the projectId here.
  • serviceAccountEmail, privateKey, projectId: Sensitive info. I hardcoded them in the example, but I recommend:
  • Add the JSON file you downloaded from Firebase to your project (e.g., serviceAccount.json).
  • Or create a Config.plist file and use that.

3. Fetching the FCM Token

func getFcmToken() async -> String? {
return await withCheckedContinuation { continuation in
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM token: \(error)")
continuation.resume(returning: nil)
} else {
continuation.resume(returning: token)
}
}
}
}

This function retrieves the device’s FCM token. I made it async to leverage Swift’s modern concurrency features. If there’s an error, I return nil; otherwise, I return the token. Here’s how to use it:

Task {
if let token = await FCMManager.shared.getFcmToken() {
print("Device token: \(token)")
} else {
print("Couldn’t fetch token, something went wrong!")
}
}

4. Sending a Notification to a Single Device

func sendNotification(to token: String, title: String, body: String, completion: @escaping (Result<String, Error>) -> Void) {
getAccessToken { result in
switch result {
case .success(let accessToken):
self.sendFCMNotification(token: token, title: title, body: body, accessToken: accessToken, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}

I use this when I want to send a notification to one device. First, I get an access token (getAccessToken), then send the notification with it. Usage is simple:

FCMManager.shared.sendNotification(to: "device_token", title: "Hello!", body: "How are you?") { result in
switch result {
case .success(let message):
print(message) // "Notification sent successfully"
case .failure(let error):
print("Error occurred: \(error)")
}
}

5. Sending Notifications to Multiple Devices

func sendNotificationToMultipleTokens(tokens: [String], title: String, body: String, completion: @escaping (Result<String, Error>) -> Void) {
getAccessToken { result in
switch result {
case .success(let accessToken):
self.sendFCMMulticastNotification(tokens: tokens, title: title, body: body, accessToken: accessToken, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}

This function kicks in when I want to send notifications to multiple devices. I send a separate request for each token and return the result once they’re all done. Example usage:

let tokens = ["token1", "token2", "token3"]
FCMManager.shared.sendNotificationToMultipleTokens(tokens: tokens, title: "Group Message", body: "Hello everyone!") { result in
switch result {
case .success(let message):
print(message) // "All notifications sent successfully"
case .failure(let error):
print("Error: \(error)")
}
}

6. Getting the Access Token (getAccessToken)

private func getAccessToken(completion: @escaping (Result<String, Error>) -> Void) {
let url = "https://oauth2.googleapis.com/token"
let scope = "https://www.googleapis.com/auth/firebase.messaging"
let now = Int(Date().timeIntervalSince1970)
let claims = FirebaseClaims(
iss: serviceAccountEmail,
scope: scope,
aud: url,
iat: now,
exp: now + 3600
)
// JWT creation and request sending...
}

This function generates a token to access Firebase’s APIs. I sign the JWT with SwiftJWT and send it to Google’s OAuth 2.0 endpoint. If successful, I get an accessToken. You don’t need to call this manually — it runs automatically within the notification functions.

7. Notification Sending Details

  • For a Single Token (sendFCMNotification):
  • I prepare the message in JSON format: title, body, and target token.
  • I send it to Firebase via an HTTP POST request.
  • For Multiple Tokens (sendFCMMulticastNotification):
  • I send a separate request for each token and collect any errors. Once all are done, I report the result.

How to Use It?

1. Add to Your Project:

2. Update Sensitive Info:

  • Go to the Firebase console, navigate to Project Settings > Service Accounts, generate a new private key, and download the JSON file.
  • Extract client_email, private_key, and project_id from the JSON.
  • Put them in a Config.plist file and fetch them from your code.

3. Send Notifications:

  • For a single device: Call sendNotification.
  • For multiple devices: Use sendNotificationToMultipleTokens.

4. Error Handling:

  • Check the success or error messages returned with Result.

Things to Watch Out For

  • Security: Don’t hardcode sensitive info like privateKey or serviceAccountEmail in your code! Add Config.plist to .gitignore so it doesn’t get pushed to Git.
  • Error Management: I print errors to the console, but you could show an alert to inform the user.
  • Performance: When sending to multiple tokens, I make a separate request for each. If the token count is high, consider moving this to a server.

Happy coding, and may your SwiftUI journey be filled with joy and success! 🚀✨

Social Networks:

GitHub: https://github.com/GkhKaya

Linkedin: https://www.linkedin.com/in/gokhan-kaya-10b140249/

If this article contains a visual that violates copyright, please contact: [email protected].

gkhkaya
gkhkaya