Home Coding Flutter Push Notification Message / APNs และ Firebase Cloud Messaging (FCM)

Flutter Push Notification Message / APNs และ Firebase Cloud Messaging (FCM)

by khomkrit

การส่ง Push Notification Message ไปยัง Flutter App ด้วย Firebase Cloud Messaging (FCM) เป็นคำถามที่ถูกถามมาโดยตลอด และทุกครั้งที่ถูกถามก็จะหา link ต่างๆ ส่งไปให้ แต่ก็มักได้คำตอบมาว่าทำตามไม่ได้ ซึ่งก็ไม่แน่ใจว่าเพราะสาเหตุใดกันแน่ บทความนี้จึงสรุปเก็บไว้ให้ เพื่อแสดงให้เห็นว่าเราสามารถเริ่มต้นใช้งานมันได้อย่างไม่ยากเย็น

Setup โปรเจ็ค

ใช้แค่ 4 ขั้นตอนนี้เท่านั้นในการทำให้รับ Message ได้

1. สร้าง Project

  1. สร้าง Firebase Project แล้วไม่ต้องทำอะไรต่อให้จำชื่อโปรเจ็คไว้ให้ดี
  2. สร้าง Flutter Project ด้วยคำสั่ง
flutter create fcm_example

2. แก้ไข Application ID

  1. เปิดไฟล์ android/app/build.gradle และกำหนดค่า applicationId เป็น id ของแอปที่เราต้องการ เช่น com.khomkrit.app
  2. เปิดไฟล์ ios/Runnder.xcodeproj/project/pbxproj และไฟล์ macos/Runner/Configs/AppInfo.xcconfig และกำหนดค่า PRODUCT_BUNDLE_IDENTIFIER เป็น id ของแอปที่เราต้องการ เช่น com.khomkrit.app

3. Auto Configure Firebase

รันคำสั่งพวกนี้มันจะสร้าง firebase config ในโปรเจ็คของเรา พร้อมกับไปสร้าง project ใน firebase console ให้เลยอัตโนมัติ

dart pub global activate flutterfire_cli
flutterfire configure

แล้วทำตามขั้นตอนไปเรื่อยๆ จนจบ จากนั้นตรวจผลลัพธ์ที่ได้จากการรัน configure จาก 2 ที่ต่อไปนี้

Firebase Console — เข้าไปดูที่ Project Settings ของโปรเจ็คเราใน firebase console ที่แท็บ General และ Cloud Messaging จะต้องมีแอปเราถูกสร้างขึ้นมา และมี application ID ตรงกับที่เราต้องการ

Flutter Project — มีไฟล์ lib/firebase_options.dart และเช็คว่ามี bundle id ตรงกับที่เราต้องการ

4. ลองส่ง Push Notification ไปที่ Android Device

ติดตั้ง dependency ด้วยคำสั่งนี้

flutter pub add firebase_core
flutter pub add firebase_messaging

เพิ่มโค้ดนี้ลงไปในฟังก์ชั่น main() และอย่าลืม import dependency ที่เกี่ยวข้อง

WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
debugPrint('device token = ${await FirebaseMessaging.instance.getToken()}');

เมื่อรันโปรเจ็คไปที่ Android Device เราจะเห็น device token ออกมาทาง console ให้ copy ค่านี้ไว้

กลับไปที่หน้า Home เพื่อพับแอปเก็บไว้ก่อน

ลองส่ง Message ที่ Firebase Console โดยไปที่เมนู Engage → Cloud Messaging แล้วเลือก Send your first message (ณ ตอนที่เขียนบทความนี้ UI เป็นแบบนี้ ในอนาคตอาจต่างกันออกไป) และกรอก device token ลงไปเพื่อระบุเครื่องปลายทางที่จะรับ

เรารับ Push Notification Message ได้แล้วโดยที่แทบไม่ต้องเขียนโค้ดอะไรเลย

เขียนโค้ดรอรับ Push Notification Message

เราจะใช้ 2 ฟังก์ชั่น คือ

  1. onMessage — สำหรับรอรับ Message ที่ได้รับตอนที่เปิดแอปใช้งานอยู่
  2. onBackgroundMessage — สำหรับรอรับ Message ที่ได้รับตอนที่แอปรันอยู่ใน Background

เพิ่มฟังก์ชั่นนี้ลงไป เอาวางไว้ข้างนอกคลาส

Future<void> firebaseMessagingHandler(RemoteMessage message) async {
  debugPrint('Notification: ${message.notification?.title ?? ''}');
}

เพิ่มฟังก์ชั่นนี้ลงไป และเรียกใช้ในระหว่าง initState

Future<void> _initFirebase() async {
  FirebaseMessaging.onMessage.listen(firebaseMessagingHandler);
  FirebaseMessaging.onBackgroundMessage(firebaseMessagingHandler);
}

จากนั้นลองส่ง Push Notification Message จาก Firebase Console ใหม่ จะพบว่า firebaseMessagingHandler ถูกเรียกทั้งตอนที่แอปอยู่ใน foreground และ background ตามต้องการ

Handle การเปิดแอปขึ้นมาจาก Push Notification

เพิ่มฟังก์ชั่นนี้ลงไป แล้วเรียกใช้หลังจาก _initFirebase()

void _setupInteractedMessage() async {
  // Open from a terminated state
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  RemoteMessage? initialMessage = await messaging.getInitialMessage();
  setState(() => _message = initialMessage?.notification?.title ?? '');

  // Open app from the background state
  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    setState(() => _message = message.notification?.title ?? '');
  });
}

เขียนโค้ดแสดงค่าจากตัวแปร _message ที่หน้าจอ

จากนั้นรันแอปขึ้นมา แล้วปิดแอป ลองส่ง Notification Message และกดเปิดแอปเข้ามาจาก Notification จะเห็นค่าในตัวแปร _message เปลี่ยนไปตาม Notification Message ที่ได้รับ

ทำให้ใช้งานได้กับ Apple iOS / APNs

ทำตาม Guideline นี้ ซึ่งจะแบ่งออกเป็น 2 ส่วน คือ

  1. Configure จาก Xcode โดยเปิดไฟล์ ios/Runner.xcworkspace ด้วย Xcode ขึ้นมาแก้
  2. Link APNs กับ FCM (Firebase Cloud Messaging) — และไม่จำเป็นต้องทำส่วน Advanced ใน Guideline

โหลด GoogleService-Info.plist จาก Firebase Console: Project Settings → General → Apple apps แล้วนำไปใส่ไว้ใน Xcode Project ตามรูป

หลังจากทำตาม Guideline ดังกล่าวแล้ว ให้เปิดไฟล์ ios/Podfile และแก้ค่าของ platform เป็น :ios, '14.0' แล้วรันโปรเจ็คไปที่ iOS Device

เพิ่มฟังก์ชั่นนี้ลงไป แล้วเรียกใช้ในฟังก์ชั่น _initFirebase() ก่อน จากนั้นถึงจะ get device token

Future<bool> _requestPermission() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    badge: true,
    provisional: false,
    sound: true,
  );

  return (settings.authorizationStatus == AuthorizationStatus.authorized ||
      settings.authorizationStatus == AuthorizationStatus.provisional);
}

ดังนั้น ตอนนี้ฟังก์ชั่น _initFirebase() ก็จะเป็นแบบนี้แล้ว

Future<void> _initFirebase() async {
  bool allow = await _requestPermission();

  if (!allow) return;

  debugPrint('device token = ${await FirebaseMessaging.instance.getToken()}');

  FirebaseMessaging.onMessage.listen(_firebaseMessagingHandler);
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingHandler);
}

เพียงเท่านี้ iOS ก็สามารถรับ Push Message ได้แล้ว

ส่ง Push Notification Message ไป Firebase Cloud Messaging เองจาก Node.js

สร้าง และโหลด Private Key ที่ Firebase Console: Project settings → Service accounts

ติดตั้ง package firebase-admin และ init Firebase Admin SDK ดังนี้

var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

และลองส่ง Mesasge เช่น

admin.messaging().sendMulticast({
  tokens: ["token", "token"],
  notification: {
    title: "Weather Warning!",
    body: "A new weather warning has been issued for your location.",
  },
  data: {'key':'value'}
})

ส่งแล้ว iOS ไม่ได้รับข้อความ?

  • ให้ลอง get APNs token มา ถ้าได้ค่าเป็น null แปลว่าการ config ทางฝั่ง Apple ไม่ถูกต้อง
await FirebaseMessaging.instance.getAPNSToken();
  • ลองทำตาม Guideline FCM via APNs Integration ใหม่อีกรอบ อย่างละเอียด อย่าข้าม
  • ลบแอปลงใหม่ และเช็คเรื่อง Permission จาก Settings ของ iOS เอง ว่าเคยไม่ให้หรือไม่?
  • เช็ค Bundle ID ให้ตรงกันหมด ทั้งจาก Firebase Console และจาก Apple Developer Portal / App Identity

ถ้าได้ APNs Token มาแล้ว แต่ก็ยังส่ง Message ผ่าน FCM ไม่ได้อีก?

ให้ลองเปลี่ยนมาใช้ APNs token ที่ได้ มาใช้ส่ง Message ผ่าน node-apn แทนที่จะใช้ Device Token ของ FCM ดู

เอาโค้ดนี้ไปรัน ถ้าส่งได้ แปลว่าอาจมีบางอย่างผิดปกติที่ FCM

var apn = require('node-apn');
var options = {
  token: {
    key: "/path/to/key.p8",
    keyId: "key-id",
    teamId: "team-id"
  },
  production: false
};
 
var apnProvider = new apn.Provider(options);

var note = new apn.Notification();
 
note.badge = 3;
note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
note.payload = {'messageFrom': 'John Appleseed'};
note.topic = "my.app.id";

let deviceToken = "apns-token";
apnProvider.send(note, deviceToken).then( (result) => {
  console.log(result);
});

Dependency

  • firebase_core: ^1.10.6
  • firebase_messaging: ^11.2.4
  • firebase-admin”: “^10.0.1”
  • Flutter 2.8.0
  • Dart 2.15.0

โค้ดในบทความนี้เป็นแค่ตัวอย่างเพื่อช่วยให้สบายใจว่าสามารถส่ง Push Notification Message หากันแล้วรับได้เท่านั้น เวลาที่ต้องทำจริง ยังมีสิ่งอื่นที่ต้องทำเพิ่มอีก

Download Example Source Code

You may also like