iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Push Notifications

Push notifications on React Native are split into two systems: FCM on Android and APNs on iOS. Most teams use Firebase Cloud Messaging as the unified API (it can deliver to APNs as well). The flow is the same: request permission, get a device token, send it to your backend, and your backend sends notifications via FCM.

FCM setup, token registration, foreground handling

EXAMPLE
// npm i @react-native-firebase/app @react-native-firebase/messaging
import messaging from '@react-native-firebase/messaging';
import { Platform, Alert } from 'react-native';
import { PermissionsAndroid } from 'react-native';

// 1) iOS + Android 13+ require explicit permission
export async function requestPushPermission() {
  if (Platform.OS === 'android' && Platform.Version >= 33) {
    const r = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
    );
    if (r !== PermissionsAndroid.RESULTS.GRANTED) return false;
  }
  const authStatus = await messaging().requestPermission();
  return (
    authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
    authStatus === messaging.AuthorizationStatus.PROVISIONAL
  );
}

// 2) Get the device token and ship it to your backend
export async function registerForPush(userId: string) {
  const ok = await requestPushPermission();
  if (!ok) return;

  const token = await messaging().getToken();
  await fetch('/api/devices', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ userId, token, platform: Platform.OS }),
  });

  // Refresh handler — tokens rotate after backup restore, reinstall, etc.
  messaging().onTokenRefresh(async (fresh) => {
    await fetch('/api/devices', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ userId, token: fresh, platform: Platform.OS }),
    });
  });
}

// 3) Handle messages — foreground, background, and tap-to-open
messaging().onMessage(async (remote) => {
  // App in foreground: notifications do NOT pop a banner by default.
  // Show your own in-app banner here.
  Alert.alert(remote.notification?.title ?? 'New message', remote.notification?.body);
});

messaging().setBackgroundMessageHandler(async (remote) => {
  // App in background or killed: this runs in a JS context with no UI.
  // Use it for data-only notifications to update local cache, badges, etc.
  await saveToLocalCache(remote.data);
});

// 4) Cold-start: did the user tap a notification to launch the app?
messaging()
  .getInitialNotification()
  .then((remote) => {
    if (remote?.data?.route) navigation.navigate(remote.data.route);
  });

// 5) Backend (Node admin SDK) — send to a specific token or topic
// import admin from 'firebase-admin';
// await admin.messaging().send({
//   token: '<device-token>',
//   notification: { title: 'Order shipped', body: 'Tap to track' },
//   data: { route: 'OrderDetail', orderId: '7001' },
//   android: { priority: 'high' },
//   apns: { headers: { 'apns-priority': '10' } },
// });

async function saveToLocalCache(data: any) { /* AsyncStorage / SQLite */ }

Why it matters

Send the device token to the backend keyed by user, not by install. Tokens rotate, so always overwrite-on-conflict and treat onTokenRefresh as the source of truth. A backend that keeps stale tokens both wastes FCM quota and miscounts active devices in dashboards.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import * as Notifications from 'expo-notifications';
const { data: token } = await Notifications.getExpoPushTokenAsync();
console.log('expo push token:', token);
Try it Yourself »

Discussion

Loading…