Expo Push Notifications ב-React Native 2026: מדריך התקנה מלא עם FCM v1 ו-APNs

מדריך מקיף 2026 להגדרת Expo Push Notifications ב-React Native: יצירת Development Build, חיבור FCM v1 דרך Service Account, הגדרת APNs ל-iOS, ניהול Notification Channels וטיפול ב-Tickets ו-Receipts בפרודקשן.

Expo Push Notifications 2026: הגדרת FCM v1 ו-APNs

בואו נדבר רגע בכנות: Push Notifications ב-React Native ב-2026 זה כבר לא מה שהיה. אם ניסיתם להריץ דחיפה דרך Expo Go ב-Android בשבוע האחרון וקיבלתם איזו אזהרה מוזרה (או גרוע מזה – שום דבר, שתיקה רועמת), אתם ממש לא לבד. החל מ-Expo SDK 53, התמיכה ב-Push דרך Expo Go ב-Android פשוט נעלמה, וגוגל מצידה החליפה את ה-FCM Legacy הוותיק ב-FCM HTTP v1 שמחייב OAuth מבוסס Service Account. במדריך הזה נעבור צעד-צעד על ההגדרה המלאה – מ-Development Build, דרך FCM v1 ו-APNs, ועד לטיפול ב-Receipts בפרודקשן. הכל עם דוגמאות קוד עובדות.

מה השתנה ב-2026: השינוי הקריטי שאי אפשר להתעלם ממנו

אז הינה השורה התחתונה שכל מפתח React Native צריך לקלוט השנה: Expo Go לא תומך יותר ב-Push Notifications מרחוק ב-Android. נקודה. התמיכה הוצאה משימוש ב-SDK 52 והוסרה לחלוטין ב-SDK 53. אם ניסיתם לבדוק התראות דרך אפליקציית Expo Go הציבורית מה-Play Store – זו בדיוק הסיבה ש"זה פשוט לא עובד".

אין דרך עוקפת. חייבים Development Build. הבשורה הטובה? התראות מקומיות (scheduleNotificationAsync) עדיין רצות בלי בעיה ב-Expo Go, אז אם כל מה שאתם צריכים זה תזכורות מתוזמנות – אתם בסדר. אבל ברגע שאתם רוצים שהשרת שלכם ידחוף הודעות מהצד השני, אין מנוס: צריך לבנות עם expo-dev-client.

למה גוגל בכלל הכריחה את המעבר ל-FCM v1?

הפרוטוקול הישן של FCM עבד עם Server Key סטטי – בעצם מחרוזת ארוכה אחת שאתם הדבקתם בשרת והיא חיה לנצח. נוח, נכון? נכון מדי, לטעמי. דליפה אחת ולתוקף יש מסיבה פתוחה למכשירים שלכם, ללא תאריך תפוגה. הפרוטוקול החדש (FCM HTTP v1) מחליף את הסיפור הזה ב-OAuth קצר-מועד מבוסס Service Account של Firebase. כל בקשה חותמת אסימון נפרד שתוקפו דקות בודדות, וזה גם מעלה משמעותית את האבטחה וגם פותח דלת לפיצ'רים מתקדמים יותר – תזמון אזורי, multi-tenant וכו'.

התקנה: בונים את הפרויקט מאפס

שלב 1: מתקינים את החבילות

שלוש חבילות, וזה הבסיס לכל הזרימה:

npx expo install expo-notifications expo-device expo-constants
  • expo-notifications – ה-API המרכזי לטיפול בכל מה שקשור להתראות.
  • expo-device – כדי לוודא שאתם רצים על מכשיר פיזי (סימולטורים, כידוע, לא מקבלים Push).
  • expo-constants – לשליפת ה-projectId שדרוש ל-getExpoPushTokenAsync.

שלב 2: יוצרים Development Build

אם זו הפעם הראשונה שאתם בונים build מותאם אישית, ההזרמה היא בערך כזו:

npx expo install expo-dev-client
npx expo prebuild
npx expo run:ios     # או
npx expo run:android

הבחירה בין הרצה מקומית לבין EAS Build תלויה בפלטפורמה. ל-iOS Production תזדקקו ל-EAS, פשוט כי בלי חשבון Apple Developer בתשלום אי אפשר לחתום על האפליקציה (ברוכים הבאים לעולם של אפל). ל-Android, לעומת זאת, בנייה מקומית בדרך כלל מספיקה לחלוטין.

שלב 3: מגדירים את ה-Notification Handler

לפני כל קריאה אחרת ל-API – שימו לב, כל קריאה אחרת – חייבים להגדיר את ה-handler. ב-SDK 53 השדות שונו: shouldShowAlert הופרד ל-shouldShowBanner ו-shouldShowList ב-iOS 14+:

import * as Notifications from 'expo-notifications';

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

ה-handler הזה הוא שמחליט איך התראה תוצג כשהאפליקציה ב-foreground. בלעדיו? התראות שמגיעות בזמן שהמשתמש ממש משתמש באפליקציה פשוט נעלמות בשקט. ראיתי את זה אצל לא מעט קולגות שכתבו שעות של קוד ניפוי באגים לפני שהבינו שזה היה כל הסיפור.

מגדירים FCM v1 ל-Android

שלב 4א: מורידים את google-services.json

היכנסו ל-Firebase Console > Project Settings > Your apps, בחרו את אפליקציית Android עם ה-package name הנכון, ולחצו Download google-services.json. רגע קטן של זהירות: הקובץ הזה הוא לא ה-Admin SDK Key. הקובץ הנכון יקרא תמיד בדיוק google-services.json, ושום שם אחר.

שלב 4ב: יוצרים Service Account Key

באותו מסך של Project Settings, עברו לטאב Service accounts, לחצו Generate new private key, ותקבלו קובץ JSON עם סיומת אקראית. הקובץ הזה רגיש – אסור בשום פנים ואופן להכניס אותו ל-git! הוסיפו אותו ל-.gitignore מיד, עוד לפני שאתם עושים git add. (סיפור אמיתי: ראיתי repo של חברה שצריכה הייתה לבטל מפתח חצי שעה אחרי commit ראשון. כאב ראש מיותר.)

שלב 4ג: מעלים ל-EAS

eas credentials

בחרו Android > production > Google Service Account > Manage your Google Service Account Key for Push Notifications (FCM V1) > Set up a Google Service Account Key > Upload a new service account key. כן, אני יודע – הנתיב ארוך מדי. EAS תשמור את המפתח באופן מאובטח ותשתמש בו בעת השליחה.

שלב 4ד: מעדכנים את app.json

{
  "expo": {
    "android": {
      "package": "com.yourcompany.yourapp",
      "googleServicesFile": "./google-services.json"
    },
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#ffffff",
          "defaultChannel": "default"
        }
      ]
    ]
  }
}

אגב האייקון: ב-Android הוא חייב להיות PNG בלבן עם רקע שקוף. אחרת תקבלו ריבוע אפור עצוב או אייקון לבן-על-לבן בלתי נראה. גם זו טעות שראיתי כמה פעמים יותר ממה שהיה בריא.

מגדירים APNs ל-iOS

שלב 5: יוצרים מפתח APNs

ב-iOS Production תזדקקו לקובץ .p8 – מפתח APNs מ-Apple Developer Account. הדרך הקלה ביותר היא להריץ:

eas credentials

לבחור iOS > production > Push Notifications: Manage your Apple Push Notifications Key, ולתת ל-EAS לייצר אוטומטית את המפתח דרך App Store Connect (תזדקקו לאישור 2FA, תכינו את המכשיר).

אם אתם מעדיפים בנייה ידנית: צרו את המפתח ב-Apple Developer Portal, הורידו את ה-.p8 (זמין רק להורדה אחת! אחת!), והעלו אותו ל-EAS.

שלב 6: יוצרים Notification Channels ב-Android

זה אולי הסעיף החשוב ביותר במדריך הזה. ב-Android 8.0+, התראות פשוט נדחות בשקט אם אין Channel. אין שגיאה. אין log. שום דבר. הכל נראה תקין מבחוץ – אבל המשתמש לא רואה כלום. צרו channel לפני בקשת ה-token:

import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';

async function setupAndroidChannel() {
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'התראות כלליות',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
      sound: 'default',
    });
  }
}

שימו לב טוב: ברגע שיצרתם channel, אפשר לשנות רק את השם והתיאור שלו. importance, sound ו-vibration נעולים לתמיד. אם אתם צריכים לשנות אותם – אין ברירה אלא למחוק את ה-channel ולייצר חדש עם ID שונה.

קבלת Push Token והרשאות

שלב 7: בקשת הרשאות וקבלת ExpoPushToken

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';

async function registerForPushNotificationsAsync() {
  if (!Device.isDevice) {
    alert('Push Notifications דורשים מכשיר פיזי');
    return;
  }

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

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

  if (finalStatus !== 'granted') {
    alert('המשתמש לא נתן הרשאה להתראות');
    return;
  }

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

  try {
    const token = (
      await Notifications.getExpoPushTokenAsync({ projectId })
    ).data;
    console.log('Expo Push Token:', token);
    return token;
  } catch (error) {
    console.error('שגיאה בקבלת Token:', error);
  }
}

הרשאות ב-Android 13+

החל מ-Android 13 (API 33), המשתמש חייב לאשר באופן מפורש את הרשאת POST_NOTIFICATIONS. requestPermissionsAsync דואג לכל הסיפור הזה אוטומטית, אבל זכרו את החוק הזה היטב: ב-iOS, אם המשתמש סירב פעם אחת – זהו, נגמר הסיפור. אי אפשר לבקש שוב, וצריך להפנות אותו ידנית ל-Settings.

שליחת התראות: Tickets ו-Receipts

שלב 8: שולחים דרך Expo Push Service

אחרי שהשגתם את ה-Expo Push Token של המשתמש ושמרתם אותו במסד הנתונים שלכם, שליחת התראה היא בקשת POST פשוטה למדי:

// Node.js backend
async function sendPushNotification(expoPushToken, title, body, data = {}) {
  const message = {
    to: expoPushToken,
    sound: 'default',
    title,
    body,
    data,
    channelId: 'default', // קריטי ל-Android
  };

  const response = await fetch('https://exp.host/--/api/v2/push/send', {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Accept-encoding': 'gzip, deflate',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(message),
  });

  return response.json();
}

חשוב מאוד: ההבדל בין Ticket ל-Receipt

זו הטעות הנפוצה ביותר אצל מתחילים, ולמען האמת – גם אצל לא מעט ותיקים: Ticket עם status "ok" לא אומר שההתראה הגיעה למשתמש. ממש לא. זה אומר רק שהשרת של Expo קיבל את הבקשה לעיבוד.

  • Ticket – מוחזר מיד. מאשר רק שה-Queue של Expo קיבל את ההתראה.
  • Receipt – זמין כ-15 דקות לאחר מכן. הוא זה שמאשר אם FCM/APNs באמת קיבלו ושלחו בפועל.
async function checkReceipts(ticketIds) {
  const response = await fetch('https://exp.host/--/api/v2/push/getReceipts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: ticketIds }),
  });

  const { data: receipts } = await response.json();

  for (const id in receipts) {
    const receipt = receipts[id];
    if (receipt.status === 'error') {
      console.error(`שגיאה בקבלה ${id}:`, receipt.message);
      if (receipt.details?.error === 'DeviceNotRegistered') {
        // הסר את ה-token הזה מבסיס הנתונים שלך
      }
    }
  }
}

טיפול בהתראות נכנסות באפליקציה

הוסיפו listeners כדי לטפל הן בהתראות שנכנסות בזמן ריצה, והן באלו שהמשתמש בפועל לוחץ עליהן:

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

export function useNotificationListener() {
  const notificationListener = useRef();
  const responseListener = useRef();

  useEffect(() => {
    notificationListener.current = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log('התראה התקבלה:', notification);
      }
    );

    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        const { data } = response.notification.request.content;
        // ניתוב לפי data.screen, data.id וכו'
        console.log('משתמש לחץ על ההתראה:', data);
      }
    );

    return () => {
      notificationListener.current?.remove();
      responseListener.current?.remove();
    };
  }, []);
}

שגיאות נפוצות (וכיצד פותרים אותן)

"זה עובד ב-Development אבל לא ב-Production"

הסיבה הכי-הכי שכיחה לזה: לא העליתם את ה-Service Account Key או את ה-APNs key ל-EAS לפני בניית ה-production build. הריצו eas credentials ובדקו שהמפתחות מסומנים תחת ה-Application Identifier הנכון. תאמינו לי – זה כמעט תמיד הבעיה.

"ה-Token לא חוזר באפליקציית הפרודקשן"

ודאו ש-projectId נשלח כמו שצריך ל-getExpoPushTokenAsync. ב-Production builds, expoConfig לא תמיד זמין, אז כדאי להשתמש ב-Constants.easConfig?.projectId כ-fallback (כפי שעשינו בקוד למעלה).

"DeviceNotRegistered" בקבלות

הטוקן נמחק. או שהמשתמש הסיר את האפליקציה, או שביטל את ההרשאות, או משהו ביניהם. חייבים להסיר את הטוקן הזה ממסד הנתונים שלכם – אחרת תנסו לשלוח אליו לנצח, וזה רק יבזבז קריאות API.

שאלות נפוצות (FAQ)

למה Push Notifications לא עובדים ב-Expo Go ב-Android?

החל מ-Expo SDK 53, גוגל הסירה את התמיכה ב-FCM Legacy ו-Expo Go האנדרואידי לא יכולה להשתמש ב-FCM v1 הסטנדרטי. הפתרון היחיד הוא לבנות Development Build באמצעות expo-dev-client. וזה לא באג שמישהו יתקן – זו החלטה אדריכלית.

האם חייבים EAS כדי לשלוח Push Notifications?

חובה? לא. מומלץ מאוד? לגמרי כן. EAS מנהלת את אישורי ה-FCM v1 וה-APNs באופן מאובטח. אפשר לעשות את זה ידנית באמצעות eas credentials בלבד גם בלי EAS Build, אבל ניהול ידני של ה-OAuth tokens של Service Account הוא, איך לומר... כאב ראש משמעותי.

מה ההבדל בין getExpoPushTokenAsync ל-getDevicePushTokenAsync?

getExpoPushTokenAsync מחזיר טוקן בפורמט ExponentPushToken[xxxx] שמיועד לעבור דרך שרת ה-Expo Push. getDevicePushTokenAsync, לעומתו, מחזיר את הטוקן הנייטיב של FCM/APNs לשליחה ישירה. הכלל הפשוט: השתמשו ב-Expo token, אלא אם יש לכם תשתית push קיימת שדורשת אחרת.

למה ההתראה מגיעה אבל בלי צליל או תצוגה?

שני אשמים אפשריים. אחד: ב-Android 8+, ללא setNotificationChannelAsync עם importance: MAX, ההתראה תגיע אבל תהיה דוממת ולא תקפוץ. שניים: setNotificationHandler חייב להגדיר shouldPlaySound: true ו-shouldShowBanner: true כדי שהתראות יוצגו כשהאפליקציה פתוחה. בדקו את שניהם.

אפשר לבדוק Push Notifications בסימולטור iOS?

חלקית בלבד. החל מ-Xcode 14, הסימולטור תומך ב-APNs דרך גרירת קובץ .apns, אבל זה לא אמין במיוחד וממש לא עובד ב-CI. אני ממליץ בחום לא לסמוך על זה. עבור local notifications (scheduleNotificationAsync) הסימולטור עובד מצוין, אבל push מרחוק – חייבים מכשיר פיזי. אין דרך אחרת.

סיכום

אז כן, הגדרת Push Notifications ב-React Native ב-2026 מורכבת יותר מבעבר, אין מה לעשות. אבל ברגע שתופסים את הזרימה הכוללת – Dev Build > Service Account > Channel > Token > Ticket > Receipt – הכל פתאום מסתדר במקום. הימנעו מהמלכודת הקלאסית של בדיקה ב-Expo Go ב-Android, השתמשו ב-FCM v1 דרך eas credentials, ותמיד-תמיד-תמיד בדקו receipts בפרודקשן כדי לתפוס טוקנים מתים בזמן. עם ההגדרה הנכונה, Expo Push Notifications היא בעיניי הדרך הנקייה ביותר לשלוח התראות, בלי לנהל לבד תשתית FCM ו-APNs מקבילה. בהצלחה.

אודות הכותב Editorial Team

Our team of expert writers and editors.