Push Notifications
A Skip framework for cross-platform push notifications on iOS and Android.
- iOS: Uses the native
UserNotificationsframework and APNs. - Android: Communicates directly with Google Mobile Services (GMS) via the C2DM registration intent protocol to obtain FCM tokens, requiring only that GMS (Google Play Services) is installed on the device.
Add the dependency to your Package.swift file:
let package = Package( name: "my-package", dependencies: [ .package(url: "https://source.skip.dev/skip-notify.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipNotify", package: "skip-notify") ]) ])Fetching a Push Notification Token
Section titled “Fetching a Push Notification Token”import SkipNotify
do { // On Android, pass your Firebase project's numeric sender ID. // On iOS, the parameter is ignored (APNs uses bundle ID + entitlements). let token = try await SkipNotify.shared.fetchNotificationToken( firebaseProjectNumber: "123456789" ) print("Push token: \(token)") // Send this token to your backend server to target this device} catch { print("Failed to get push token: \(error)")}Finding your Firebase project number:
Open the Firebase console ↗,
select your project, go to Project settings > Cloud Messaging,
and copy the Sender ID (a numeric string like "123456789012").
On iOS, the returned token is the APNs device token as a hex string.
On Android, the returned token is an FCM registration token — the same
token that FirebaseMessaging.getInstance().token would produce.
Checking GMS Availability
Section titled “Checking GMS Availability”Before requesting a token on Android, you can check whether Google Mobile Services is available:
if SkipNotify.shared.isGMSAvailable { let token = try await SkipNotify.shared.fetchNotificationToken( firebaseProjectNumber: "123456789" )} else { print("GMS not available: \(SkipNotify.shared.gmsStatusDescription)")}| Property | iOS | Android (with GMS) | Android (without GMS) |
|---|---|---|---|
isGMSAvailable | false | true | false |
gmsStatusDescription | "GMS not applicable (iOS)" | "GMS available" | "GMS not available" |
How It Works
Section titled “How It Works”Standard APNs registration flow:
- Calls
UIApplication.shared.registerForRemoteNotifications() - Receives the device token via the
didRegisterForRemoteNotificationsWithDeviceTokenapp delegate callback (bridged throughNotificationCenter) - Returns the token as a hex-encoded string
Your app delegate must forward the token and error callbacks to
NotificationCenter. Skip projects created with skip init or
skip create include this automatically. For custom setups, add
the forwarding calls documented in the source.
Android
Section titled “Android”SkipNotify communicates directly with Google Mobile Services using
the C2DM registration intent protocol — the same underlying
mechanism that the firebase-messaging SDK uses internally.
The registration flow:
- Availability check: Verifies
com.google.android.gmsis installed and enabled on the device - Register a BroadcastReceiver: Listens for the
com.google.android.c2dm.intent.REGISTRATIONresponse broadcast - Send the registration intent: Sends
com.google.android.c2dm.intent.REGISTERto GMS with:app: APendingIntentthat GMS uses to verify the calling app’s package identitysender: The Firebase project number (sender ID)subtype: Same as sender (signals standard app-level registration)gmsVersion: The installed GMS version codescope:"GCM"(the registration scope)
- Receive the token: GMS responds asynchronously via the
REGISTRATIONbroadcast with aregistration_idextra containing the FCM token, or anerrorextra if registration failed
Sending a Push Notification
Section titled “Sending a Push Notification”Once you have the FCM token from fetchNotificationToken, send
messages from your server using the
FCM HTTP v1 API ↗:
POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:sendAuthorization: Bearer <OAuth2-token>Content-Type: application/json
{ "message": { "token": "<the-token-from-fetchNotificationToken>", "notification": { "title": "Hello", "body": "World" } }}For iOS, use the
APNs HTTP/2 API ↗
with the hex device token returned by fetchNotificationToken.
utilize a tool like gorush ↗ to simplify the configuration and authentication.
Configuration
Section titled “Configuration”Follow the steps described in the Registering your app with APNs ↗ documentation:
- Select your app from the App Store Connect Certificates, Identifiers & Profiles ↗ page and select “Capabilities” and turn on Push Notifications then click “Save”
- Use the Push Notifications Console ↗ to send a test message to your app.
Android
Section titled “Android”No additional Gradle dependencies or google-services.json file is required.
GMS (Google Play Services) must be installed on the device.
To receive push messages (not just register for tokens), your app
needs a BroadcastReceiver for the com.google.android.c2dm.intent.RECEIVE
action in AndroidManifest.xml:
<receiver android:name=".PushMessageReceiver" android:permission="com.google.android.c2dm.permission.SEND" android:exported="true"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> </intent-filter></receiver>The android:permission attribute is critical — it restricts delivery
to broadcasts sent by GMS (which holds the
com.google.android.c2dm.permission.SEND permission), preventing
other apps from injecting fake push messages.
Message payloads arrive as intent extras:
| Extra key | Description |
|---|---|
gcm.notification.title | Notification title |
gcm.notification.body | Notification body |
google.message_id | Unique message ID |
collapse_key | Collapse key (if set) |
| (your custom keys) | Data payload fields |
Token Refresh
Section titled “Token Refresh”GMS may invalidate registration tokens after Play Services updates or device resets. Re-register at app startup and compare the returned token against the one stored on your server. If it differs, update the server.
You can also listen for the com.google.android.c2dm.intent.REGISTRATION
broadcast in your manifest to detect token refreshes:
<receiver android:name=".TokenRefreshReceiver" android:exported="true"> <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> </intent-filter></receiver>The new token arrives in the registration_id extra of the broadcast intent.