1. Firebase 사이트에서 테스트하기
- 첫 번째 캠페인을 만들기
- Firebase 알림 메세지 만들기
- 테스트할 알림 제목, 알림 텍스트를 입력하고 전송
- Flutter 콘솔에서 Token을 검색해서 복붙 해서 토큰 추가
2. Postman으로 테스트하기
- 요청 URL
https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send
- Access Token 생성하기
- Header 설정
Authorization: Bearer YOUR_ACCESS_TOKEN // 위에서 구한 토큰값 복붙 Content-Type: application/json
- Postman 전체 설정
POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send Authorization: Bearer YOUR_ACCESS_TOKEN // 위에서 구한 토큰값 복붙 Content-Type: application/json { "message": { "token": "DEVICE_REGISTRATION_TOKEN", // Fluuter 콘솔에서 토큰 값 복붙 "notification": { "body": "This is an FCM notification message!", "title": "FCM Message" } } }
- 결과
- FCM 서버가 요청을 수신하고 메시지를 성공적으로 전송했음
3. Background Message : 앱이 열려있지만, 화면에 없을 경우
firebase_api.dart
수정
import 'package:firebase_messaging/firebase_messaging.dart'; class FirebaseApi { final _firebaseMessaging = FirebaseMessaging.instance; // 백그라운드에서 메시지 처리 함수 static Future<void> handleBackgroundMessage(RemoteMessage message) async { print('mattabu: Title: ${message.notification?.title}'); print('mattabu: Body: ${message.notification?.body}'); print('mattabu: Payload: ${message.data}'); } Future<void> initNotifications() async { // 사용자에게 알림 권한 요청 (배지, 알림, 소리 옵션 설정) await _firebaseMessaging.requestPermission( badge: true, // 배지 표시 허용 alert: true, // 알림 표시 허용 sound: true, // 알림 소리 허용 ); final FCMToken = await _firebaseMessaging.getToken(); print('Token: $FCMToken'); FirebaseMessaging.onMessage.listen((RemoteMessage message) { print('Message received: ${message.notification?.body}'); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { print('Message clicked: ${message.notification?.body}'); }); FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage); } }
- main.dart에 백그라운드에서 수신된 메세지를 처리하는 로직 추가
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'firebase_api.dart'; import 'firebase_options.dart'; final homeUrl = Uri.parse("https://www.example.com"); // 웹뷰에 로드할 URL 설정 // 백그라운드에서 수신된 메시지를 처리하는 로직 Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { print('Handling a background message: ${message.messageId}'); } void main() async { WidgetsFlutterBinding.ensureInitialized(); // Flutter의 위젯 시스템 초기화 await Firebase.initializeApp( // Firebase 초기화 (비동기 함수) options: DefaultFirebaseOptions.currentPlatform, // 현재 플랫폼에 맞는 Firebase 설정 적용 ); // 백그라운드 메시지 핸들러 등록 FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); // Firebase 알림 초기화 (필요한 경우) await FirebaseApi().initNotifications(); runApp(const MyApp()); // MyApp 실행 } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'Flutter Demo', // 앱의 제목 설정 theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // 앱의 테마 색상 설정 useMaterial3: true, // Material 3 사용 설정 ), home: const HomePage(), // 앱의 홈 화면으로 HomePage 위젯 설정 ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); // 상태(State) 클래스 생성 } class _HomePageState extends State<HomePage> { late final WebViewController controller; // WebView 컨트롤러 선언 @override void initState() { super.initState(); controller = WebViewController() // WebView 컨트롤러 초기화 ..setJavaScriptMode(JavaScriptMode.unrestricted) // 자바스크립트 허용 모드 설정 ..addJavaScriptChannel( 'Toaster', // 'Toaster'라는 이름의 JavaScript 채널 추가 onMessageReceived: (JavaScriptMessage msg) async { // WebView의 자바스크립트에서 보낸 메시지를 처리하는 콜백 함수 }, ) ..loadRequest(homeUrl); // 설정된 URL 로드 } @override Widget build(BuildContext context) { return Scaffold( body: WebViewWidget(controller: controller), // WebView를 표시할 위젯 ); } }
Foreground Message : 앱이 열려있고, 화면을 차지하고 있는 경우
flutter_local_notifications
설치
flutter_local_notifications: ^17.2.2
-
firebase_api.dart
수정
import 'dart:convert'; // jsonEncode를 사용하기 위한 import import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // 백그라운드에서 메시지를 처리할 함수 Future<void> handleBackgroundMessage(RemoteMessage message) async { print('백그라운드 메시지 제목: ${message.notification?.title}'); print('백그라운드 메시지 본문: ${message.notification?.body}'); print('백그라운드 메시지 데이터: ${message.data}'); } class FirebaseApi { // Firebase Messaging 인스턴스 생성 final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; // FlutterLocalNotificationsPlugin 인스턴스 생성 final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin(); // Android 알림 채널 설정 final AndroidNotificationChannel _androidChannel = const AndroidNotificationChannel( 'high_importance_channel', 'High Importance Notifications', description: '이 채널은 알림을 표시하는 데 사용됩니다.', importance: Importance.defaultImportance, ); // 알림 초기화 함수 Future<void> initNotifications() async { // 사용자에게 알림 권한 요청 (배지, 알림, 소리 옵션 설정) await _firebaseMessaging.requestPermission( badge: true, // 배지 표시 허용 alert: true, // 알림 표시 허용 sound: true, // 알림 소리 허용 ); // 이 기기의 FCM 토큰을 가져옴 final String? FCMToken = await _firebaseMessaging.getToken(); // FCM 토큰을 출력 (서버로 전송하거나 디버깅에 사용) print('FCM 토큰: $FCMToken'); // 앱이 백그라운드에 있을 때 메시지를 처리할 함수 등록 FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage); // 알림 초기화 및 설정 추가 await initPushNotifications(); await initLocalNotification(); } // 로컬 알림 초기화 함수 Future<void> initLocalNotification() async { // Android 초기화 설정 const AndroidInitializationSettings android = AndroidInitializationSettings('ic_launcher'); const InitializationSettings settings = InitializationSettings(android: android); // 로컬 알림 초기화 await _localNotifications.initialize(settings); // Android 플랫폼에 맞게 알림 채널 생성 final AndroidFlutterLocalNotificationsPlugin? platform = _localNotifications.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>(); await platform?.createNotificationChannel(_androidChannel); } // 메시지 처리 함수 추가 (앱이 열려있을 때) void handleMessage(RemoteMessage? message) { // 메시지가 null인 경우 아무 작업도 하지 않음 if (message == null) return; print('포그라운드 메시지: $message'); } // 푸시 알림 설정 함수 추가 Future<void> initPushNotifications() async { // 포그라운드 알림 설정 (알림, 배지, 소리 허용) await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); // 앱이 처음 시작될 때 메시지 처리 FirebaseMessaging.instance.getInitialMessage().then(handleMessage); // 알림 클릭 시 메시지 처리 FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); // 포그라운드에서 메시지 수신 시 알림 표시 FirebaseMessaging.onMessage.listen((RemoteMessage message) { print('포그라운드 메시지 수신: ${message.messageId}'); final RemoteNotification? notification = message.notification; if (notification != null) { print('알림 제목: ${notification.title}'); print('알림 본문: ${notification.body}'); // 로컬 알림을 사용하여 메시지 표시 _localNotifications.show( notification.hashCode, // 알림 ID notification.title ?? '제목 없음', // 제목 notification.body ?? '본문 없음', // 본문 NotificationDetails( android: AndroidNotificationDetails( _androidChannel.id, // 채널 ID _androidChannel.name, // 채널 이름 channelDescription: _androidChannel.description, // 채널 설명 icon: 'ic_launcher', // 알림 아이콘(확장자 제외) ), ), payload: jsonEncode(message.toMap()), // 메시지 데이터 ); } }); } }
- 아이콘 추가 방법
- android/app/src/main/res/drawable/ic_launcher.png(파일 추가)
AndroidManifest.xml
추가
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application android:label="fcm_test" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> <!-- Flutter 애플리케이션의 테마를 지정 /이 테마는 Flutter UI가 초기화된 후에도 계속 적용. --> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:showWhenLocked="true" android:turnScreenOn="true" android:usesCleartextTraffic="true"> <!-- 앱의 시작 액티비티 - MAIN 액션과 LAUNCHER 카테고리로 설정하여 앱이 실행될 때 이 액티비티가 열림 --> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <!-- FCM 알림의 기본 채널 ID를 설정 --> <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="high_importance_channel" /> </activity> <!-- Don't delete the meta-data below. This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> <meta-data android:name="flutterEmbedding" android:value="2" /> <!-- Flutter Local Notifications 플러그인과 관련된 리시버 설정 --> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" android:exported="true" /> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" /> </intent-filter> </receiver> </application> <!-- 텍스트를 처리할 수 있는 활동을 쿼리하는 데 필요한 설정 --> <queries> <intent> <action android:name="android.intent.action.PROCESS_TEXT"/> <data android:mimeType="text/plain"/> </intent> </queries> <!-- 애플리케이션에 필요한 권한 설정입니다. --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- FCM 관련 권한 설정입니다. --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/> </manifest>
- app/build.gradle
plugins { id "com.android.application" id "kotlin-android" id "com.google.gms.google-services" // Firebase 플러그인 id "dev.flutter.flutter-gradle-plugin" } android { namespace = "com.example.fcm_test" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8 } compileSdk = 34 // Flutter SDK 버전 defaultConfig { applicationId = "com.example.fcm_test" minSdk = 21 targetSdk = 33 versionCode = 1 versionName = "1.0" } buildTypes { release { signingConfig = signingConfigs.debug } } } dependencies { implementation 'com.google.firebase:firebase-messaging:24.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" }
- android/build.gradle
buildscript { ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() } dependencies { classpath 'com.google.gms:google-services:4.3.15' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(":app") } tasks.register("clean", Delete) { delete rootProject.buildDir }
Share article