SwiftUI Device to Device Push Notification with Firebase

Device to device push notification ability is not included on Firebase SDK for IOS Development project. This was the blocker for me while learning to build my messaging app. After do more research, i found a way to do that.
In just the past year, the Google Cloud Messaging platform has improved a lot. Now, you can easily send push notifications from one app to another using Swift. That can be doing by sending HTTP Request to its server. I’ll explain more about that, lets go!
Setting up SwiftUI Project
First, you need to settings your SwiftUI Project with this requirement:
- XCode Project : Allow push notification and background process
- Firebase : Create Firebase app, Add Firebase SDK to our xcodeproj, settings APN’s.
For more detailed explanation you can visit my previous article SwiftUI Push Notifications with Firebase: A Step-by-Step Guide
Try the Algorithm
The algorithm is simply sending HTTP Request to google within this domain https://fcm.googleapis.com/fcm/send. According to Firebase Documentation (https://firebase.google.com/docs/cloud-messaging/http-server-ref), we need to create a payload to send the push notification.
{
"to": "receiver device fcm token",
"notification": {
"title": "title",
"body": "body"
}
}
Not only the payload, we also need a server key from our firebase app which will be used later. To get it, you need to open Firebase Console and go to Project Settings and click Manage API in Google Cloud Console. After opening that url, click enable.

Refresh your firebase console and you can see your key.

After that, lets try it on Postman. Add Authorization
with value key=yourServerKey
and add the payload to the body.


As you can see, we’ve successfully send the push notification.

Implement with Swift
Now, we just need to implement above flow/algorithm to our app using swift and swiftui. I will use my previous article codebase for the starter.
// PushNoApp.swift
import SwiftUI
import FirebaseCore
import FirebaseMessaging
class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
application.registerForRemoteNotifications()
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let fcm = Messaging.messaging().fcmToken {
print("fcm", fcm)
}
}
}
@main
struct PushNoApp: App {
// register app delegate for Firebase setup
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// NotificationManager.swift
@MainActor
class NotificationManager: ObservableObject{
@Published private(set) var hasPermission = false
init() {
Task{
await getAuthStatus()
}
}
func request() async{
do {
try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound])
await getAuthStatus()
} catch{
print(error)
}
}
func getAuthStatus() async {
let status = await UNUserNotificationCenter.current().notificationSettings()
switch status.authorizationStatus {
case .authorized, .ephemeral, .provisional:
hasPermission = true
default:
hasPermission = false
}
}
}
Again, for more detailed explanation you can visit my previous article SwiftUI Push Notifications with Firebase: A Step-by-Step Guide.
After that, first we need to create PushNotificationManager.swift
to make it clean. The PushNotificationManager
class is responsible for sending push notifications. The static method sendPushNotification()
is the entry point for sending a push notification. The method constructs an HTTP POST request to the FCM API endpoint. This request includes the recipient's Firebase Cloud Messaging (FCM) registration token, which is stored in the receiverFCM
variable, and the server key, which is stored in the serverKey
variable. These values are used for authentication and target specification.
The method creates a URLRequest
with the appropriate URL and HTTP method (POST). It sets the necessary headers for the request, including the Authorization
header with the FCM server key and the Content-Type
header specifying JSON data. The request body is a JSON payload that includes the FCM registration token of the recipient (receiverFCM
) and a notification payload with a title and a body.
The method serializes the JSON payload into data and sets it as the request’s HTTP body. It then uses a URLSession
to send the HTTP request asynchronously. The completion handler of the data task prints any error that occurs during the request, and if the request is successful and there is a response with data, it prints the response data as a UTF-8 encoded string.
//PushNotificationManager.swift
import Foundation
class PushNotificationManager {
static func sendPushNotification() {
let receiverFCM = ""
let serverKey = ""
let url = URL(string: "https://fcm.googleapis.com/fcm/send")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// Set the request headers
request.setValue("key=\(serverKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Set the request body data
let requestBody: [String: Any] = [
"to": receiverFCM,
"notification": [
"title": "Title",
"body": "Body"
]
]
if let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) {
request.httpBody = jsonData
// Send the request
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
if let data = data {
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
}
}
}.resume()
}
}
}
Coding for User Interface
Now we are going to make the user interface to access the notification manager and try the push notification.
import SwiftUI
struct ContentView: View {
@StateObject var notificationManager = NotificationManager()
var body: some View{
VStack{
Button("Request Notification"){
Task{
await notificationManager.request()
}
}
.buttonStyle(.bordered)
.disabled(notificationManager.hasPermission)
.task {
await notificationManager.getAuthStatus()
}
Button("Send Push Notification"){
PushNotificationManager.sendPushNotification()
}
.buttonStyle(.bordered)
.disabled(notificationManager.hasPermission)
}
}
}
Test Push Notification
To test, we need 2 device. 1 is real device, we need real device to get its FCM Token and 1 more for sending the push notification.
First we will build the app to real device and get the FCM Token. Dont forget to press Request Notification.

After that, copy the fcm token to your PushNotificationManager
receiverFCM
variable

And build it again on your simulator. Press the Send Push Notification button to send it to your real device.

As you can see, after pressed that button, on our real device the notification is received

From writer
Hello, allow me to introduce myself — I’m Muhammad Rezky Sulihin. We’ve reached the end of this tutorial, and I sincerely thank you for taking the time to read it. If you have any questions or feedback, feel free to reach out to me directly via email at mrezkysulihin@gmail.com. I’m more than happy to receive your input, whether it’s about my English writing skills or about technical explanations, i might be wrong. Your insights will help me grow.
Looking forward to connecting with you in future articles! By the way, I’m a mobile developer currently learning at the Apple Developer Academy. I’m open to various opportunities such as collaborations, freelance work, internships, part-time, or full-time positions. It would bring me great happiness to explore these possibilities.
Until next time, stay curious, and keep learning!