Notificări Push în Expo: Ghid Complet pentru React Native 2026

Configurează notificări push în Expo și React Native în 2026: expo-notifications, FCM V1, APNs, obținere token, trimitere de la server și deep linking cu Expo Router. Include cod real și troubleshooting pentru producție.

Notificări Push Expo React Native Ghid 2026

Actualizat: 11 iulie 2026

Notificările push în Expo sunt mesaje trimise de la un server către dispozitivele utilizatorilor prin serviciul Expo Push Service, care abstractizează FCM V1 pentru Android și APNs pentru iOS folosind un token unic per dispozitiv. Cu expo-notifications și SDK-ul Expo 53, poți configura, primi și procesa notificări în mai puțin de 30 de minute. Există, totuși, câteva capcane critice legate de tranziția FCM Legacy către FCM V1, permisiunile iOS 18 și diferențele dintre Expo Go și build-uri EAS pe care le vom acoperi în detaliu mai jos.

  • Începând cu iunie 2024, Google a dezactivat FCM Legacy. Trebuie să folosești FCM V1 cu un fișier service-account.json încărcat prin EAS.
  • Începând cu SDK 53, notificările push nu mai funcționează în Expo Go pe Android. Testarea reală necesită un build EAS de dezvoltare.
  • Token-ul Expo Push (ExpoPushToken[...]) este diferit de token-urile native FCM/APNs; folosește Expo Push Service pentru livrare cross-platform.
  • iOS necesită o cheie APNs configurată în Apple Developer Portal și încărcată în EAS pentru build-uri de producție.
  • Handler-ul de notificări (setNotificationHandler) controlează comportamentul foreground. Fără el, notificările nu apar când aplicația este deschisă.
  • Pentru deep linking din notificări, folosește useLastNotificationResponse combinat cu Expo Router pentru navigare stabilă.

Ce sunt notificările push în Expo

Notificările push sunt mesaje asincrone livrate de la un server către dispozitivul utilizatorului chiar și când aplicația este închisă. În ecosistemul Expo, biblioteca expo-notifications oferă o abstracție unificată peste cele două servicii native de bază: Firebase Cloud Messaging (FCM V1) pe Android și Apple Push Notification service (APNs) pe iOS. În loc să comunici direct cu aceste servicii, aplicația ta obține un Expo Push Token (un identificator opaque care începe cu ExpoPushToken[...]) și îl trimite serverului tău backend.

Când vrei să trimiți o notificare, serverul face o cerere HTTP către Expo Push Service (exp.host/--/api/v2/push/send), care apoi rutează mesajul prin FCM sau APNs în funcție de platformă. Această arhitectură rezolvă trei probleme dureroase. Nu trebuie să gestionezi credențiale duale, primești o rată de livrare uniformă și beneficiezi de un dashboard pentru debugging. În 2026, cu tranziția completă la FCM V1 (Legacy a fost dezafectat pe 22 iunie 2024) și cu React Native 0.79 ca versiune de bază pentru SDK 53, fluxul este mai stabil ca oricând, dar cere atenție la configurare.

Există trei tipuri distincte de notificări pe care le poți gestiona: notificări locale (programate pe dispozitiv fără server), notificări remote push (trimise prin Expo Push Service) și data-only push (fără UI, folosite pentru sincronizare în background). Cele mai multe aplicații folosesc combinație de locale și remote.

Instalare și configurare expo-notifications

Hai să începem cu partea practică. Într-un proiect Expo existent (SDK 53+) instalezi pachetul cu comanda oficială Expo, care rezolvă versiunea compatibilă automat:

npx expo install expo-notifications expo-device expo-constants

Adaugă apoi plugin-ul în app.json pentru a genera corect configurația nativă la build-time. Un lucru pe care l-am învățat pe pielea mea: iconița de notificare pentru Android trebuie să fie o imagine PNG albă cu fundal transparent, altfel Android va afișa un pătrat gri urât.

{
  "expo": {
    "name": "MyApp",
    "slug": "my-app",
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#4F46E5",
          "defaultChannel": "default",
          "sounds": ["./assets/notification-sound.wav"]
        }
      ]
    ],
    "ios": {
      "bundleIdentifier": "com.mycompany.myapp",
      "infoPlist": {
        "UIBackgroundModes": ["remote-notification"]
      }
    },
    "android": {
      "package": "com.mycompany.myapp",
      "googleServicesFile": "./google-services.json"
    }
  }
}

Pe iOS, notificările push sunt disponibile doar pe dispozitive fizice. Simulatorul, pur și simplu, nu poate primi push-uri de la APNs. Pentru testare rapidă, poți totuși simula recepția cu Notifications.scheduleNotificationAsync folosind un trigger de tip null, care execută imediat notificarea local.

Cum obții un Expo push token

Token-ul push Expo este identificatorul unic al dispozitivului tău în sistemul Expo. Îl obții cu getExpoPushTokenAsync, dar înainte trebuie să ceri permisiunea utilizatorului și, în cazul EAS, să pasezi projectId-ul din configurație. Iată un hook complet, gata de folosit, care gestionează întregul flux:

import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
import { useEffect, useState } from 'react';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true,
    shouldShowList: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export function usePushToken() {
  const [token, setToken] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    registerForPushNotificationsAsync()
      .then(setToken)
      .catch((err) => setError(err.message));
  }, []);

  return { token, error };
}

async function registerForPushNotificationsAsync() {
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'Default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#4F46E5',
    });
  }

  if (!Device.isDevice) {
    throw new Error('Notificările push necesită un dispozitiv fizic');
  }

  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== 'granted') {
    throw new Error('Permisiunea pentru notificări nu a fost acordată');
  }

  const projectId =
    Constants?.expoConfig?.extra?.eas?.projectId ??
    Constants?.easConfig?.projectId;

  if (!projectId) {
    throw new Error('Project ID nu a fost găsit în configurație');
  }

  const tokenData = await Notifications.getExpoPushTokenAsync({ projectId });
  return tokenData.data;
}

Token-ul returnat arată ca ExpoPushToken[xxxxxxxxxxxxxxxxxxxxxx] și trebuie salvat pe serverul tău, asociat cu ID-ul utilizatorului. Token-urile se pot invalida (utilizatorul dezinstalează aplicația, resetează dispozitivul, refuză permisiunile). Expo Push Service va returna erori DeviceNotRegistered în răspunsul batch, iar tu trebuie să le ștergi din baza de date pentru a nu risipi cereri.

Configurare FCM V1 pentru Android

Google a dezafectat definitiv API-ul FCM Legacy HTTP pe 22 iunie 2024, iar Expo Push Service folosește exclusiv FCM V1 începând cu SDK 51. Diferența practică e simplă. În loc să încarci o server key simplă în Expo Dashboard, acum trebuie să generezi un fișier service-account.json din Firebase Console și să-l urci prin EAS. Vezi detaliile în documentația oficială Firebase pentru migrarea la FCM V1.

Pașii sunt:

  1. Creează un proiect în Firebase Console și adaugă o aplicație Android cu package name-ul din app.json.
  2. Descarcă fișierul google-services.json și pune-l în root-ul proiectului Expo. Adaugă calea la android.googleServicesFile în app.json.
  3. În Firebase Console, mergi la Project Settings → Service Accounts și generează o cheie privată nouă. Rezultatul este un JSON de tip service-account.json.
  4. Urcă acest fișier la Expo prin CLI:
eas credentials
# Selectează platforma Android
# Selectează "Google Service Account"
# Selectează "Manage your Google Service Account Key for Push Notifications (FCM V1)"
# Upload the service-account.json file

După încărcare, Expo va folosi acest service account pentru a autentifica cererile către FCM V1 în locul tău. Nu ai nevoie de nimic în plus în cod, abstracția rămâne aceeași.

Configurare APNs pentru iOS

Pentru iOS, ai nevoie de un cont Apple Developer plătit (99$/an) și o cheie APNs. Din 2026, Apple recomandă folosirea cheilor .p8 (token-based) în locul certificatelor .p12 vechi, pentru că expiră mai rar și pot fi folosite pentru mai multe aplicații.

  1. Mergi la Apple Developer Portal → Keys și creează o cheie nouă cu Apple Push Notifications service (APNs) bifat.
  2. Descarcă fișierul AuthKey_XXXXXXXXXX.p8 și notează Key ID și Team ID.
  3. Rulează eas credentials și selectează iOS, apoi Add a Push Key. Introdu Key ID, Team ID și calea către fișierul .p8.

Din SDK 53, Expo generează automat capability-ul Push Notifications în profilul de provisioning la eas build. Dacă folosești bare workflow sau un config custom, verifică că UIBackgroundModes include remote-notification în Info.plist.

Trimiterea notificărilor de la server

Expo Push Service acceptă cereri HTTPS POST către https://exp.host/--/api/v2/push/send. Poți trimite până la 100 de mesaje într-un singur batch, ceea ce e recomandat pentru scalabilitate. Iată un exemplu în Node.js folosind SDK-ul oficial expo-server-sdk-node:

import { Expo, ExpoPushMessage } from 'expo-server-sdk';

const expo = new Expo({
  accessToken: process.env.EXPO_ACCESS_TOKEN,
  useFcmV1: true,
});

async function sendPushNotifications(
  tokens: string[],
  title: string,
  body: string,
  data: Record<string, unknown> = {}
) {
  const messages: ExpoPushMessage[] = [];

  for (const pushToken of tokens) {
    if (!Expo.isExpoPushToken(pushToken)) {
      console.warn(`Token invalid: ${pushToken}`);
      continue;
    }

    messages.push({
      to: pushToken,
      sound: 'default',
      title,
      body,
      data,
      priority: 'high',
      channelId: 'default',
    });
  }

  const chunks = expo.chunkPushNotifications(messages);
  const tickets = [];

  for (const chunk of chunks) {
    try {
      const chunkTickets = await expo.sendPushNotificationsAsync(chunk);
      tickets.push(...chunkTickets);
    } catch (error) {
      console.error('Eroare la trimiterea batch-ului:', error);
    }
  }

  return tickets;
}

Fiecare mesaj primește un ticket cu status ok sau error. Pentru un audit complet, verifică receipts-urile după 15 până la 30 de minute cu expo.getPushNotificationReceiptsAsync(ticketIds). Acolo vei vedea erori reale de la FCM/APNs (token invalid, credențiale expirate, mesaj respins).

Dacă vrei tuning fin pentru performanță pe partea client, uită-te și la ghidul pentru FlashList v2. Combinația cu notificări e frecventă în aplicații de tip inbox.

Gestionarea notificărilor primite

Există trei stări în care aplicația ta poate primi o notificare, fiecare necesitând un handler diferit. Sincer, fără configurare, notificările apar doar când aplicația este în background. În foreground vor fi ignorate silent, ceea ce este cel mai comun bug pe care îl întâlnesc utilizatorii noi (l-am prins și eu la primul proiect în care am folosit expo-notifications).

import * as Notifications from 'expo-notifications';
import { useEffect, useRef } from 'react';

export function useNotificationListeners() {
  const notificationListener = useRef<Notifications.Subscription>();
  const responseListener = useRef<Notifications.Subscription>();

  useEffect(() => {
    // Aplicația în foreground primește o notificare
    notificationListener.current = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log('Notificare primită:', notification.request.content);
      }
    );

    // Utilizatorul a apăsat pe notificare (aplicația poate fi background/killed)
    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        const data = response.notification.request.content.data;
        console.log('Utilizator a apăsat pe notificare:', data);
      }
    );

    return () => {
      if (notificationListener.current) {
        Notifications.removeNotificationSubscription(notificationListener.current);
      }
      if (responseListener.current) {
        Notifications.removeNotificationSubscription(responseListener.current);
      }
    };
  }, []);
}

Handler-ul global setat cu setNotificationHandler (arătat mai sus la secțiunea de token) controlează ce se întâmplă cu notificările în foreground. Începând cu [email protected], câmpurile s-au schimbat: shouldShowAlert a fost divizat în shouldShowBanner și shouldShowList. Dacă folosești tutoriale mai vechi, vei primi warning-uri de deprecation.

Deep linking din notificări cu Expo Router

Cel mai frecvent scenariu practic este acesta: utilizatorul primește o notificare despre un mesaj nou, apasă pe ea și trebuie să ajungă direct în conversația respectivă. Cu Expo Router, poți folosi useLastNotificationResponse pentru a gestiona atât cazul cold start (aplicația era închisă), cât și cazul warm resume:

import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';

export function useNotificationRouter() {
  const lastResponse = Notifications.useLastNotificationResponse();

  useEffect(() => {
    if (!lastResponse) return;

    const data = lastResponse.notification.request.content.data as {
      screen?: string;
      params?: Record<string, string>;
    };

    if (data?.screen) {
      router.push({
        pathname: data.screen,
        params: data.params ?? {},
      });
    }
  }, [lastResponse]);
}

Trimite payload-ul data din server cu structura așteptată de router: { "screen": "/chat/[id]", "params": { "id": "42" } }. Pentru rute private care necesită autentificare, vezi cum funcționează rutele protejate în Expo Router cu Stack.Protected. Combinația e critică pentru a evita redirect-uri false când sesiunea a expirat.

Testare în producție și troubleshooting

Cel mai rapid mod de a testa un token e cu Expo Push Notifications Tool. Lipești token-ul, scrii un mesaj și primești răspunsul instant. Pentru testare automată, integrarea în CI/CD poate folosi endpoint-ul de dry-run.

Metodă testareNecesarLatențăCând o folosești
Expo Push Tool webDoar token1 la 3 secDebugging manual, prototipare
expo-server-sdk (Node)Backend Node2 la 5 secIntegrare în producție
Notificări localeDoar dispozitivInstantTestare UI foreground
Firebase Console directToken FCM nativ1 la 5 secBypass Expo pentru debug FCM
APNs direct (curl)Cheie .p8, Team ID1 la 3 secDebug probleme iOS specific

Probleme frecvente și soluțiile lor:

  • Token nu se generează: Verifică că projectId este pasat explicit și că aplicația nu rulează în simulator iOS.
  • Notificări livrate cu întârziere de ore: Priority-ul e normal, schimbă la high în payload.
  • Notificări nu apar pe iOS producție: Cheia APNs e legată de Team ID greșit sau Bundle ID nu se potrivește.
  • Pe Android APK nu funcționează: google-services.json lipsește sau package name-ul nu se potrivește cu Firebase.
  • Doar unii utilizatori nu primesc: Verifică receipts. Probabil DeviceNotRegistered; șterge token-urile expirate.

Pentru debugging avansat pe dispozitiv, combină acest ghid cu React Native DevTools. Poți inspecta timeline-ul cererilor de rețea și evenimentele de notificare în același panou.

Întrebări frecvente

Cum trimit notificări push în React Native fără Expo?

Poți folosi direct Firebase Cloud Messaging (FCM V1) prin @react-native-firebase/messaging pentru Android și integrare nativă APNs pentru iOS. E mult mai complex, pentru că necesită gestionarea a două token-uri diferite, două SDK-uri și logica de rutare pe server. Expo Push Service abstractizează toate acestea, dar poți face self-hosted dacă ai nevoie de control total.

Funcționează notificările push Expo fără Firebase?

Pe Android, nu. Începând cu 2024, Firebase Cloud Messaging V1 este singurul serviciu suportat de Google pentru push, deci ai nevoie de un proiect Firebase și google-services.json. Pe iOS, Firebase nu este necesar; folosești direct APNs cu cheia .p8. Poți avea aplicația doar cu APNs configurat dacă lansezi doar pe iOS.

Care este diferența dintre notificări locale și push în Expo?

Notificările locale sunt programate direct pe dispozitiv cu scheduleNotificationAsync, fără server, internet sau token. Push notifications sunt trimise de la un server extern prin Expo Push Service, FCM sau APNs. Locale sunt ideale pentru remindere și alarme; push pentru evenimente care se întâmplă în afara dispozitivului (mesaje noi, tranzacții).

De ce nu primesc notificări push în Expo Go?

Începând cu SDK 53, Expo Go pe Android nu mai suportă notificări push remote din cauza restricțiilor FCM V1 (Google nu permite ca un pachet client să servească mai multe aplicații). Trebuie să folosești eas build --profile development pentru un development build care include propriul google-services.json. Pe iOS, Expo Go acceptă în continuare notificările push, dar doar pentru dezvoltare.

Cât costă folosirea Expo Push Service în producție?

Expo Push Service este gratuit și nelimitat pentru majoritatea cazurilor de utilizare, fără quota per utilizator sau per notificare. Rata limită este 600 cereri/secundă per proiect. FCM și APNs sunt de asemenea gratuite. Costurile apar doar dacă folosești EAS Build pentru CI/CD (după quota gratuită) sau dacă ai backend propriu pentru trimitere.

Despre Autor Editorial Team

Our team of expert writers and editors.