
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:
- Copy the file into your project.
- Add the libraries via SPM:
- FirebaseMessaging: https://github.com/firebase/firebase-ios-sdk.git
- SwiftJWT: https://github.com/Kitura/Swift-JWT.git
- In Xcode, go to File > Add Packages, add these URLs, select FirebaseMessaging and SwiftJWT, and include them in 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].