بازگشت به وبلاگ

پوش نوتیفیکیشن در Flutter؛ دریافت پیام و مسیریابی اعلان

پوش نوتیفیکیشن در Flutter؛ دریافت پیام و مسیریابی اعلان

برای افزودن پوش نوتیفیکیشن به اپلیکیشن Flutter، مسیر قابل اتکا استفاده از firebase_messaging و پیکربندی رسمی Firebase برای هر دو پلتفرم است. در این روش، برنامه توکن FCM را دریافت می‌کند، بک‌اند شما آن را به یک شناسه مشترک یا کاربر مرتبط می‌سازد و ارسال پیام از یک سرور امن انجام می‌شود؛ نه از داخل اپلیکیشن.

در این مقاله:

پوشفا در این معماری می‌تواند لایه ارسال و مدیریت مخاطبان باشد: این سرویس از FCM، موضوع‌ها (Topics)، توکن‌های ذخیره‌شده، نام مستعار، گزارش ارسال و کلیک و APIهای سروری پشتیبانی می‌کند. بااین‌حال، برای Flutter نباید پکیج یا SDK فرضی مانند pushfa_flutter معرفی کرد؛ اتصال اپ باید بر پایه Firebase و کد بک‌اند واقعی پروژه انجام شود.

معماری صحیح ارسال پوش در Flutter

توابع sendTokenToBackend و routeFromPush در نمونه‌ها باید در برنامه خودتان پیاده شوند؛ اولی ثبت امن توکن در backend شما و دومی مسیریابی معتبر و کنترل دسترسی را انجام می‌دهد. در جریان معمول، Firebase برای اپلیکیشن اندروید و iOS ثبت می‌شود و هر نصب برنامه یک توکن FCM دریافت می‌کند. اپلیکیشن این توکن را همراه با شناسه کاربر احراز‌شده به بک‌اند می‌فرستد. بک‌اند نیز باید توکن را ذخیره، با تغییرات آن به‌روزرسانی و هنگام خروج کاربر یا نامعتبرشدن توکن، غیرفعال کند.

کلیدهای خصوصی ارسال هرگز نباید در کد Flutter، فایل APK، فایل IPA یا مرورگر قرار بگیرند. اپ فقط اطلاعات عمومی و توکن لازم را ارسال می‌کند؛ تصمیم درباره مخاطب، زمان ارسال، لینک مقصد و اعتبارسنجی باید در سرور انجام شود. اگر از پوشفا استفاده می‌کنید، بک‌اند شما می‌تواند توکن‌ها را به مخاطبان ذخیره‌شده در آن سرویس متصل کند و از پنل، موضوع‌ها، نام‌های مستعار، گزارش ارسال و گزارش کلیک بهره ببرد.

برای لینک مقصد، یک نشانی مشخص یا مسیر داخلی مانند /orders/123 را در داده پیام قرار دهید. در کمپین‌های تحلیلی می‌توان پارامترهای UTM ساخت، اما نباید اطلاعات شخصی را داخل URL گذاشت. برای آشنایی با طراحی مسیر کلیک، مقاله دیپ‌لینک در پوش نوتیفیکیشن نیز مفید است.

تنظیم پروژه برای اندروید

  1. در Firebase یک پروژه بسازید و اپ اندروید را با همان نام بسته نهایی برنامه ثبت کنید.
  2. فایل google-services.json را از Firebase دریافت و طبق ساختار نسخه فعلی پروژه Flutter در بخش اندروید قرار دهید.
  3. پلاگین Google Services و وابستگی‌های Firebase را مطابق مستندات نسخه Flutter و Gradle پروژه فعال کنید. شماره نسخه قدیمی را بدون بررسی به‌صورت ثابت در مقاله یا پروژه کپی نکنید.
  4. در AndroidManifest.xml مجوز اینترنت را داشته باشید و برای اندروید ۱۳ و بالاتر، مجوز زمان اجرای اعلان را در زمان مناسب از کاربر درخواست کنید.

از اندروید ۸، اعلان‌ها به کانال وابسته‌اند. کانال را با شناسه ثابت و نام قابل فهم بسازید و سطح اهمیت آن را بر اساس نوع پیام انتخاب کنید؛ کاربر می‌تواند کانال را جداگانه خاموش یا محدود کند. در اندروید ۱۳ نیز داشتن مجوز در مانیفست به‌تنهایی کافی نیست و باید درخواست زمان اجرا انجام شود. جزئیات این محدودیت‌ها در مستندات مجوز اعلان اندروید توضیح داده شده است.

تنظیم پروژه برای iOS

  1. در Firebase اپ iOS را با Bundle ID دقیق پروژه ثبت کنید و فایل GoogleService-Info.plist را به هدف صحیح در Xcode اضافه کنید.
  2. در بخش Signing & Capabilities قابلیت Push Notifications و در صورت نیاز Background Modes مربوط به اعلان از راه دور را فعال کنید.
  3. در حساب Apple Developer، کلید یا گواهی APNs بسازید و آن را در تنظیمات Firebase Cloud Messaging قرار دهید.
  4. در برنامه، پیش از دریافت توکن، اجازه اعلان، صدا و نشان برنامه را با requestPermission درخواست کنید.

مجوز را بلافاصله هنگام نخستین اجرای برنامه نمایش ندهید؛ بهتر است ابتدا کوتاه توضیح دهید کاربر چه نوع پیام مفیدی دریافت خواهد کرد و سپس در واکنش به یک اقدام مستقیم او درخواست را نشان دهید. وضعیت مجوز ممکن است مجاز، ردشده یا موقت باشد و باید در رابط کاربری و منطق ارسال در نظر گرفته شود.

راه‌اندازی Firebase و دریافت توکن

ابتدا وابستگی‌های رسمی firebase_core و firebase_messaging را با نسخه سازگار با پروژه خود اضافه کنید. در مسیر FlutterFire، ابتدا پیکربندی پروژه را با ابزار رسمی انجام دهید تا فایل firebase_options.dart و DefaultFirebaseOptions تولید شوند. نمونه زیر بخش اتصال در برنامه شماست؛ MyApp از برنامه موجود می‌آید. سپس Firebase را پیش از اجرای رابط کاربری مقداردهی کنید:

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

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

پس از مقداردهی، در زمان مناسب مجوز را بگیرید و توکن را بخوانید:

final messaging = FirebaseMessaging.instance;

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

if (settings.authorizationStatus == AuthorizationStatus.authorized) {
  final token = await messaging.getToken();
  if (token != null) {
    await sendTokenToBackend(token);
  }
}

messaging.onTokenRefresh.listen(sendTokenToBackend);

تابع sendTokenToBackend در این مثال باید یک تابع واقعی در لایه شبکه برنامه باشد که توکن را به سرور شما می‌فرستد. توکن را به‌عنوان شناسه دائمی کاربر فرض نکنید؛ نصب مجدد، پاک‌شدن داده‌ها، تغییر محیط یا شرایط سرویس می‌تواند باعث تغییر آن شود. راهنمای رسمی Flutter برای تفاوت رویدادهای foreground و background و قواعد مجوزها را می‌توان در مستندات دریافت پیام در Flutter بررسی کرد.

مدیریت foreground، background و اجرای بسته

وقتی برنامه در foreground است

وقتی اپلیکیشن باز و در foreground است، نمایش اعلان روی نوار اعلان معمولاً به‌صورت خودکار انجام نمی‌شود. رویداد FirebaseMessaging.onMessage را دریافت کنید و برای نمایش محلی، از یک راهکار اعلان محلی سازگار با Android و iOS استفاده کنید. در اندروید، این نمایش باید به یک کانال معتبر متصل باشد. مراقب باشید یک پیام را هم خودتان نمایش ندهید و هم مسیر دیگری دوباره نمایش ندهد.

وقتی برنامه در background یا بسته است

پیام دارای بخش notification می‌تواند در background توسط سیستم‌عامل نمایش داده شود؛ پیام data-only برای اجرای منطق سفارشی به محدودیت‌های سیستم‌عامل و عمر پردازش پس‌زمینه وابسته است. بنابراین نباید فرض کرد هر کد دلخواهی در پس‌زمینه یا پس از بسته‌شدن کامل برنامه اجرا می‌شود.

هندلر پس‌زمینه باید تابعی سطح‌بالا، مستقل از رابط کاربری و دارای نقطه ورود قابل حفظ باشد:

@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  // فقط کار کوتاه و غیررابط‌کاربری انجام دهید.
}

در این هندلر عملیات سنگین، دسترسی مستقیم به context، ناوبری یا تغییر ویجت‌ها انجام ندهید. اگر هدف صرفاً نمایش اعلان است، از نوع پیام و رفتار سیستم‌عامل استفاده کنید؛ اگر داده حساس یا منطق فوری دارید، محدودیت‌های Android و iOS را جداگانه آزمایش کنید.

نمایش اعلان و مسیریابی پس از کلیک

کلیک روی اعلان یک رویداد جدا از تحویل پیام است. برای برنامه‌ای که در background بوده، از FirebaseMessaging.onMessageOpenedApp استفاده کنید؛ برای حالتی که برنامه کاملاً بسته بوده، مقدار getInitialMessage() را هنگام شروع بررسی کنید.

FirebaseMessaging.onMessageOpenedApp.listen((message) {
  routeFromPush(message.data);
});

final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
  routeFromPush(initialMessage.data);
}

در مثال فرضی، routeFromPush باید نوع مسیر را اعتبارسنجی کند، منتظر آماده‌شدن ناوبر بماند و سپس کاربر را به صفحه مورد نظر ببرد. اگر پیام به سفارش، گفتگو یا محتوای نیازمند ورود اشاره می‌کند، ابتدا وضعیت احراز هویت و وجود منبع را بررسی کنید. گزارش کلیک یا بازشدن مسیر نیز به‌معنای مطالعه قطعی محتوا نیست؛ تنها نشان می‌دهد رویداد کلیک یا بازشدن در مسیر ثبت شده است.

چرخه عمر توکن و چک‌لیست تست

در بک‌اند برای هر توکن زمان ثبت، آخرین زمان مشاهده، پلتفرم، نسخه برنامه و وضعیت فعال را نگه دارید. با رویداد onTokenRefresh مقدار جدید را جایگزین کنید و هنگام خروج کاربر، ارتباط توکن با حساب را طبق مدل حریم خصوصی خود حذف یا غیرفعال کنید. یک نصب ممکن است چند توکن در طول زمان داشته باشد؛ به همین دلیل ارسال را فقط به یک رکورد قدیمی محدود نکنید.

برای آزمایش، پیام‌ها را با متن کوتاه شروع کنید. عنوان پوشفا حداکثر ۳۵ و متن آن حداکثر ۱۵۰ کاراکتر باشد. اگر از زمان‌بندی یا صف استفاده می‌کنید، منقضی‌شدن پیام با تحویل تضمین‌شده تفاوت دارد؛ TTL فقط بازه تلاش برای تحویل هنگام آفلاین‌بودن را تعیین می‌کند. همچنین collapse_id می‌تواند برای پیام‌های قابل جایگزینی مفید باشد، اما رفتار ادغام حمل‌ونقل با جایگزینی اعلان نمایش‌داده‌شده در همه پلتفرم‌ها یکسان نیست.

  • اندروید ۱۳: مجوز اعلان پذیرفته شده و کانال روشن است.
  • اندروید: برنامه روی دستگاهی با سرویس‌های لازم و نسخه انتشار واقعی آزمایش شده است.
  • iOS: دستگاه واقعی، APNs معتبر و مجوز کاربر بررسی شده است.
  • هر سه وضعیت foreground، background و terminated جداگانه تست شده‌اند.
  • کلیک از اعلان، مسیر معتبر و رفتار کاربر بدون ورود را پوشش می‌دهد.
  • توکن جدید در سرور ذخیره و توکن‌های نامعتبر از ارسال‌های بعدی حذف می‌شوند.
  • کلید خصوصی فقط در بک‌اند قرار دارد و در لاگ یا کد موبایل چاپ نمی‌شود.

اگر پیام ارسال می‌شود اما روی دستگاه دیده نمی‌شود، ابتدا مجوز، کانال، توکن فعلی، نوع پیام و وضعیت برنامه را بررسی کنید؛ صرف موفقیت درخواست ارسال، به‌تنهایی اثبات نمایش یا خواندن پیام نیست. برای عیب‌یابی عمومی‌تر می‌توانید مقاله چرا پوش ارسال می‌شود اما دریافت نمی‌شود؟ را بخوانید.

پرسش‌های متداول

آیا برای Flutter به SDK اختصاصی پوشفا نیاز است؟

برای این مسیر، خیر. از firebase_messaging و تنظیمات رسمی Firebase استفاده کنید و اتصال به پوشفا را در بک‌اند، با سرویس و مستندات واقعی حساب خود، انجام دهید. پکیج یا تابعی را که در پنل یا مستندات رسمی ارائه نشده است به پروژه اضافه نکنید.

چرا اعلان در foreground دیده نمی‌شود؟

این وضعیت معمولاً طبیعی است. رویداد onMessage را دریافت کنید و با یک راهکار اعلان محلی، نمایش را به‌صورت کنترل‌شده انجام دهید؛ در عین حال از نمایش دوباره همان پیام جلوگیری کنید.

آیا یک توکن FCM برای همیشه معتبر می‌ماند؟

خیر. توکن را مقدار موقتی برای یک نصب در نظر بگیرید، رویداد تغییر توکن را ثبت کنید و هنگام پاسخ نامعتبر از سرویس ارسال، آن رکورد را غیرفعال یا حذف کنید.

آیا تحویل موفق یعنی کاربر پیام را خوانده است؟

خیر. نمایش موفق اعلان یا ثبت کلیک فقط یک رویداد فنی است و نمی‌تواند مطالعه یا درک محتوای پیام را ثابت کند.