Bare React Native Recipe
Register a bare React Native app's APNs and FCM tokens with Native Notify's push API using react-native-firebase/messaging, and show the Android data messages with Notifee.
A bare React Native app (no Expo modules) usually already has Firebase wired in for Android and an APNs registration for iOS. Both tokens register with the same endpoint. The example uses @react-native-firebase/messaging for the tokens, @notifee/react-native to display Android pushes, and react-native-device-info for a stable device id.
Register the device
import { PermissionsAndroid, Platform } from "react-native";
import messaging from "@react-native-firebase/messaging";
import notifee, { AndroidImportance } 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 messaging().requestPermission();
}
// iOS: the raw APNs token (null until APNs has delivered it). Android: the FCM token.
const token = Platform.OS === "ios" ? await messaging().getAPNSToken() : await messaging().getToken();
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 messaging().onTokenRefresh((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,
});
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" } },
});
}
Call registerUniversalDevice({ appId, appToken, subscriberId }) on every launch (an idempotent upsert — same deviceId, same device) and keep watchUniversalToken(...) subscribed while the app runs.
- iOS:
getAPNSToken()resolves the raw APNs token as a hex string — ornulluntil APNs has delivered it, in which case the next launch registers. - Android:
onTokenRefreshreports the rotated FCM token, which is registered again under the same device.
Show Android pushes
Native Notify's Android pushes are FCM data messages (title, message, and body = your pushData as a JSON string, plus channelId / subtitle / … when the send set them). Firebase does not display data messages itself — 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 messaging from "@react-native-firebase/messaging";
import { showNativeNotifyMessage } from "./universalPush";
if (Platform.OS === "android") {
messaging().onMessage(showNativeNotifyMessage);
messaging().setBackgroundMessageHandler(showNativeNotifyMessage);
}
On iOS the system displays the APNs alert itself. Taps: the push comes straight from APNs, not through FCM, so Firebase's tap handlers may not see it — read userInfo in the AppDelegate's UNUserNotificationCenter delegate (your pushData keys are at its top level; see the Swift recipe) and pass it to JS.
No Firebase on iOS? Use the APNs delegate directly: application(_:didRegisterForRemoteNotificationsWithDeviceToken:) hands you the token as Data; hex-encode it as the Swift recipe does and pass it to JS through a native module.
Required project setup
- Android: the app's
google-services.json(the Firebase project whose service account is saved as the app's credentials in Native Notify). - iOS: the Push Notifications capability (an
aps-environmententitlement that matches the build), and the app's.p8key saved in Native Notify with this app's bundle id. - Permission: tokens are issued without it, but nothing is shown until the user allows notifications —
requestPermission()on iOS,POST_NOTIFICATIONSon Android 13+ (both in the code above).
Send and verify
"all" reaches every registered device, with or without a subscriberId; to reach one logged-in user's devices, send to {"type":"subscribers","subscriberIds":[...]} — see Send Notifications. Prove one device first with a test send to its deviceId.
Next steps
- Device Registration and Push Credentials.
- Want to skip the native wiring? The
native-notifySDK works in bare React Native too — it requires Expo modules for the push-token plumbing (npx install-expo-modules; see Installation).