Register a bare React Native app's APNs and FCM tokens with Native Notify, show Android pushes with Notifee, and show images.
1. Set up your React Native project
npm install @react-native-firebase/app @react-native-firebase/messaging @notifee/react-native react-native-device-info
cd ios && pod install
The code uses the modular API of React Native Firebase 22 or newer. Version 26 removed messaging() and needs the New Architecture, the default since React Native 0.76.
Android needs the app's google-services.json, from the Firebase project whose service account you save in step 4. iOS needs the Push Notifications capability (in Xcode: Signing & Capabilities → + Capability), with an aps-environment entitlement that matches the build.
2. Register the device and show pushes
import { PermissionsAndroid, Platform } from "react-native";
import {
getAPNSToken,
getMessaging,
getToken,
onTokenRefresh,
requestPermission,
} from "@react-native-firebase/messaging";
import notifee, { AndroidImportance, AndroidStyle } from "@notifee/react-native";
import DeviceInfo from "react-native-device-info";
const REGISTER_URL = "https://app.nativenotify.com/api/universal/device/register";
async function postRegistration({ appId, appToken, subscriberId }, token) {
const res = await fetch(REGISTER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
appId,
appToken,
deviceId: await DeviceInfo.getUniqueId(), // IDFV on iOS, ANDROID_ID on Android
platform: Platform.OS,
subscriberId, // your logged-in user's id; omit for an anonymous device
tokens: Platform.OS === "ios" ? { apnsToken: token } : { fcmToken: token },
}),
});
if (res.status !== 201) {
// 400/403 answer { error: { code, message } }; a wrong appId/appToken answers 401 as plain text.
throw new Error(`Native Notify register failed (${res.status}): ${await res.text()}`);
}
return res.json(); // { ok, device, tokens }
}
export async function registerUniversalDevice(args) {
// Without permission nothing is shown (tokens are issued either way).
if (Platform.OS === "android" && Number(Platform.Version) >= 33) {
await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
} else if (Platform.OS === "ios") {
await requestPermission(getMessaging());
}
// iOS: the raw APNs token (null until APNs has delivered it). Android: the FCM token.
const token = Platform.OS === "ios" ? await getAPNSToken(getMessaging()) : await getToken(getMessaging());
if (!token) return null; // not ready yet — the next launch registers
return postRegistration(args, token);
}
// FCM rotates Android tokens — register the new one.
export function watchUniversalToken(args) {
return onTokenRefresh(getMessaging(), (fcmToken) => {
if (Platform.OS !== "android") return;
postRegistration(args, fcmToken).catch((err) => console.warn("Native Notify:", err));
});
}
// Android pushes are FCM *data* messages: show them yourself (foreground and background).
export async function showNativeNotifyMessage(remoteMessage) {
const data = remoteMessage.data || {};
if (!data.title) return;
const channelId = await notifee.createChannel({
id: data.channelId || "default",
name: "Notifications",
importance: AndroidImportance.HIGH,
});
// A send with bigPictureURL also carries a notification block with the image URL.
const picture = remoteMessage.notification?.android?.imageUrl;
await notifee.displayNotification({
title: data.title,
body: data.message,
data: { pushData: data.body || "{}" }, // your pushData JSON (+ nn_notification_id / nn_source)
android: {
channelId,
pressAction: { id: "default" },
...(picture ? { largeIcon: picture, style: { type: AndroidStyle.BIGPICTURE, picture } } : {}),
},
});
}
Call registerUniversalDevice({ appId, appToken, subscriberId }) on every launch, and keep watchUniversalToken(...) subscribed while the app runs.
3. Wire up the handlers
Android pushes are FCM data messages, so Firebase doesn't display them. Hand them to showNativeNotifyMessage from both handlers:
// index.js — before AppRegistry.registerComponent (the background handler must be set early)
import { Platform } from "react-native";
import { getMessaging, onMessage, setBackgroundMessageHandler } from "@react-native-firebase/messaging";
import { showNativeNotifyMessage } from "./universalPush";
if (Platform.OS === "android") {
onMessage(getMessaging(), showNativeNotifyMessage);
setBackgroundMessageHandler(getMessaging(), showNativeNotifyMessage);
}
On iOS the system shows the alert itself. A tap comes straight from APNs, so Firebase's tap handlers may not see it: read userInfo in the AppDelegate's UNUserNotificationCenter delegate and pass it to JS, as in the Swift recipe. Without Firebase on iOS, hex-encode the token from application(_:didRegisterForRemoteNotificationsWithDeviceToken:) the same way and pass it to JS through a native module.
4. Save credentials in Native Notify
In Native Notify, open your project's Settings → Push credentials and save the Firebase service-account JSON (Android) and the .p8 key with this app's bundle id (iOS). See Push credentials.
5. Send a push with the API
Check one device first with a test send, then send to an audience.
Show images
Firebase draws a push's picture itself while the app is closed or in the background, on the channel firebase.json names. Create that channel at launch:
{
"react-native": {
"messaging_android_notification_channel_id": "default"
}
}
import notifee, { AndroidImportance } from "@notifee/react-native";
// Firebase draws a closed-app push on this channel, so it must exist before the push arrives.
notifee.createChannel({ id: "default", name: "Notifications", importance: AndroidImportance.HIGH });
showNativeNotifyMessage above draws the picture while the app is open: a send with bigPictureURL carries a notification block, and remoteMessage.notification.android.imageUrl holds the URL.
On iPhone, add a notification service extension to the app's Xcode project: open ios/YourApp.xcworkspace in Xcode, then follow Show images in the Swift recipe. The iPhone Simulator never runs it, so test on an iPhone. Send a push with a bigPictureURL: an https JPEG or PNG under 1 MB.
Next steps
- Device registration — fields, deregister and errors.
- Expo setup — the
native-notifySDK also works in bare React Native, with Expo modules (npx install-expo-modules).