تطوير الويب

Push notifications في Flutter مع Firebase Cloud Messaging

4 min de lecture

السلسلة: هذا الدرس جزء من سلسلة Flutter. اقرأ المقال الرئيسي.

Push notifications هي الجسر الأكثر مباشرة بين تطبيق موبايل والمستخدم بعد الإغلاق. هذا الدرس يدمج Firebase Cloud Messaging (FCM) في تطبيق Flutter من البداية إلى النهاية: إنشاء مشروع Firebase، إعداد Android وiOS، إدارة أذون Android 13+، استرداد token، استقبال foreground وbackground، واختبار من console Firebase.

المتطلبات

  • Flutter SDK 3.41+
  • حساب Google لإنشاء مشروع Firebase
  • تطبيق Flutter أساسي
  • لـ iOS: Mac مع Xcode 15+، حساب Apple Developer (99 USD/سنة) لشهادة APNs
  • Node.js لـ FlutterFire CLI
  • 90 دقيقة

الخطوة 1 — إنشاء مشروع Firebase وتثبيت CLI

اذهب إلى console.firebase.google.com، انقر «إضافة مشروع»، أعطه اسماً، عطّل Google Analytics إن لم تحتجه، وأكّد. التوفير يأخذ 30 ثانية.

npm install -g firebase-tools
firebase login
dart pub global activate flutterfire_cli

الخطوة 2 — إضافة Firebase إلى المشروع Flutter

flutterfire configure

CLI تسألك أي مشروع Firebase، ثم أي منصّات (اختر Android وiOS على الأقل)، وتكتب ملف lib/firebase_options.dart. هذا الملف يحتوي credentials عامة للتهيئة من جانب العميل — قابل للـ commit في Git بلا مخاطرة.

flutter pub add firebase_core
flutter pub add firebase_messaging
flutter pub add flutter_local_notifications

الإصدارات: firebase_core: ^4.x.x، firebase_messaging: ^16.0.0، flutter_local_notifications: ^21.0.0. حزمة flutter_local_notifications لعرض الإشعار في شريط Android حين التطبيق foreground (FCM لا يفعل ذلك تلقائياً في هذه الحالة).

الخطوة 3 — تهيئة Firebase عند الإقلاع

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

الخطوة 4 — طلب إذن الإشعارات

منذ Android 13 (API 33)، إذن POST_NOTIFICATIONS يجب طلبه صراحة. بدون هذا الطلب، إشعاراتك تُحجَب صامتة — الخطأ الأصعب تشخيصاً.

import 'package:firebase_messaging/firebase_messaging.dart';

Future<void> setupMessaging() async {
  final messaging = FirebaseMessaging.instance;

  final settings = await messaging.requestPermission(
    alert: true,
    badge: true,
    sound: true,
    provisional: false,
  );

  switch (settings.authorizationStatus) {
    case AuthorizationStatus.authorized:
      print('autorisees');
      break;
    case AuthorizationStatus.provisional:
      print('autorisation provisoire (iOS)');
      break;
    case AuthorizationStatus.denied:
      print('refusees');
      break;
    case AuthorizationStatus.notDetermined:
      print('non encore demandee');
      break;
  }
}

الوقت الجيد لاستدعاء setupMessaging() بعد login المستخدم أو لحظة تكون فيها قيمة الإشعارات واضحة — لا الإقلاع الأول. طلب سابق لأوانه ينتهي عادةً بالرفض.

الخطوة 5 — استرداد وتخزين token FCM

final token = await FirebaseMessaging.instance.getToken();
print('FCM Token: ${token}');

// جانب backend: اربط token بالمستخدم المتصل
await api.post('/devices', body: {'fcm_token': token});

// استمع للتجديدات
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
  await api.post('/devices', body: {'fcm_token': newToken});
});

أرسل token منهجياً إلى backend بعد login. استمع onTokenRefresh — تجاهل هذا الحدث ينتج عنه tokens شبحية في backend لا تُسلِّم شيئاً.

الخطوة 6 — إدارة الاستقبال في foreground وbackground وterminé

Foreground (التطبيق مفتوح ومرئي): Android لا يعرض شيئاً تلقائياً، عليك دفع إشعار محلي أو تحديث UI.

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Foreground: ${message.notification?.title}');
});

Background (التطبيق في الخلفية): النظام يعرض الإشعار وSDK يستطيع تنفيذ كود Dart عبر handler مخصّص. يجب أن يكون دالة top-level.

@pragma('vm:entry-point')
Future<void> _backgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  print('Background: ${message.messageId}');
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  FirebaseMessaging.onBackgroundMessage(_backgroundHandler);
  runApp(const MyApp());
}

الـ annotation @pragma('vm:entry-point') تضمن أن الكود لا يُحذَف بـ tree-shaking في build release. إلزامية في AOT.

Terminé (التطبيق مغلق، أُطلق من الإشعار): الإشعار الأصلي متاح عبر getInitialMessage().

final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) {
  print('Lancee depuis notification');
  // وجّه المستخدم نحو الشاشة الملائمة
}

الخطوة 7 — إعداد Android للإشعارات

منذ Android 8 (API 26)، كل الإشعارات يجب أن تنتمي لـ channel. بدون channel، إشعاراتك لا تظهر.

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

const channel = AndroidNotificationChannel(
  'high_importance_channel',
  'Notifications importantes',
  description: 'Notifications critiques',
  importance: Importance.high,
);

final localNotifs = FlutterLocalNotificationsPlugin();

await localNotifs
    .resolvePlatformSpecificImplementation<
        AndroidFlutterLocalNotificationsPlugin>()
    ?.createNotificationChannel(channel);
FirebaseMessaging.onMessage.listen((message) {
  final notif = message.notification;
  final android = message.notification?.android;
  if (notif != null && android != null) {
    localNotifs.show(
      id: notif.hashCode,
      title: notif.title,
      body: notif.body,
      notificationDetails: NotificationDetails(
        android: AndroidNotificationDetails(
          channel.id,
          channel.name,
          icon: android.smallIcon,
        ),
      ),
    );
  }
});

الخطوة 8 — إعداد iOS وAPNs

على iOS، FCM يعمل بين خادمك وAPNs، لكن APNs تبقى البوابة النظامية — يجب تفعيل capability «Push Notifications» في Xcode وتقديم شهادة أو مفتاح APNs لـ Firebase.

  1. افتح ios/Runner.xcworkspace في Xcode (أبداً Runner.xcodeproj وحده).
  2. اختر target Runner، tab «Signing & Capabilities»، انقر «+ Capability»، أضف Push Notifications وBackground Modes.
  3. في «Background Modes»، اختر «Remote notifications».
  4. على developer.apple.com، أنشئ APNs Authentication Key (.p8). نزّلها.
  5. في console Firebase ← إعدادات المشروع ← Cloud Messaging ← iOS app configuration، حمّل المفتاح .p8 مع Key ID وTeam ID.

بدون مفتاح .p8 محمَّل في Firebase، SDK صامت على iOS — لا خطأ، لا إشعارات. أصعب فخّ في أول تكامل.

الخطوة 9 — الاختبار من console Firebase

اذهب إلى Engagement ← Messaging ← New campaign ← Notifications. عنوان ومحتوى، اختر «Send a test message»، ألصق token FCM، أكّد. الإشعار يظهر في ثوان.

إن لم يصل شيء، تحقّق بالترتيب: إذن ممنوح (Android)، channel مُنشأ، token صالح، لـ iOS الشهادة APNs محمَّلة.

تشريح إشعار FCM من backend إلى الهاتف

خادمك يستدعي API HTTPS لـ FCM بـ JSON يصف الهدف (token، topic، أو شرط منطقي) والمحتوى. FCM يستلم، يحفظ إن لزم (هاتف مطفأ يتلقى حتى 100 رسالة non-collapsible في الانتظار)، ثم يوجّه. على Android، FCM يستخدم Google Play Services. على iOS، FCM ينقل إلى APNs.

لقياس التسليم الفعلي، فعّل Google Analytics في مشروعك Firebase: يجمع الرسائل المرسَلة، الانطباعات والفتوحات.

Topics أم tokens: متى ماذا

token فردي يرسل إلى جهاز محدّد — للإشعارات المعاملاتية: تأكيد طلب، تنبيه اتصال، رمز تحقق. topic يرسل إلى كل الأجهزة المشتركة — للبثّ: ميزات جديدة، تحديثات كتالوغ.

await FirebaseMessaging.instance.subscribeToTopic('updates_fr');
await FirebaseMessaging.instance.unsubscribeFromTopic('updates_fr');

ممارسات UX ومضاد spam

  • لا تطلب الإذن أبداً عند الإقلاع الأول. انتظر اللحظة التي يفهم فيها المستخدم قيمة التطبيق.
  • إحصاءات opt-in: ~50% قبول على iOS و85% على Android (الذي يطلب الإذن منذ 13).
  • قسّم حسب النوع. مستخدم قد يريد تنبيهات stock لكن لا newsletter.
  • احترم الساعات. إشعار في 3 صباحاً لإعلان promo هو الطريقة الأنجع لخسارة مستخدم.

أخطاء شائعة

الخطأ السبب الحل
لا إشعارات على Android 13+ إذن POST_NOTIFICATIONS غير مطلوب استدع messaging.requestPermission() وتحقّق من الحالة
إشعار foreground لا يظهر FCM لا يعرض شيئاً foreground افتراضياً وصّل flutter_local_notifications في onMessage مع channel
Background handler لا يُنفَّذ في release @pragma('vm:entry-point') مفقود عنون handler top-level. لا closure
iOS: لا إشعارات إطلاقاً شهادة APNs غائبة أو capability غير مفعَّلة تحقّق capability Xcode + مفتاح .p8 محمَّل في Firebase
Token null عند الإقلاع تهيئة Firebase لم تنتهِ بعد await Firebase.initializeApp() قبل getToken()
Token يتغيّر فجأة سلوك عادي: إعادة تثبيت، restore استمع onTokenRefresh وأبلغ backend

أسئلة شائعة

هل FCM مجاني فعلاً؟ نعم، بلا سقف معروف للتوزيع البسيط. الميزات المدفوعة حول التحليلات المتقدمة، A/B testing، الجدولة بـ cohort.

هل يمكن الإرسال من backend؟ نعم، هذا الوضع العادي. backend يستدعي HTTP v1 API لـ FCM بـ service account JSON (مولَّد في console).

الفرق بين notification message وdata message؟ notification message يحوي حقل notification (عنوان، نص) يُرَسَم تلقائياً. data message أزواج key-value فقط. data message أكثر مرونة لكن أصعب على iOS حيث يلزم content-available: 1.

إدارة إلغاء الاشتراك؟ backend: احذف token من جدول devices عند logout. عميل: FirebaseMessaging.instance.deleteToken().

كيف نفتح شاشة محددة؟ ضع المعرّف في حقل data: {"data": {"screen": "order_detail", "order_id": "123"}}. اقرأ message.data['screen'] في getInitialMessage() وفي onMessageOpenedApp.

هل أحتاج جهاز حقيقي؟ لا، emulator Android مع Google Play Services يستلم. لـ iOS، simulator يدعم push منذ Xcode 14 (مايو 2022) على macOS 13+ مع Apple Silicon أو T2 — في sandbox APNs فقط. الجهاز الحقيقي يبقى المرجع.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité