Flutter 푸시 알림의 완성: Firebase와 Local Notifications 충돌 완벽 해결법
Flutter로 앱을 개발하다 보면 사용자에게 시의적절한 정보를 전달하고 재방문을 유도하는 '푸시 알림' 기능은 거의 필수적입니다. Flutter 생태계에서 푸시 알림을 구현할 때 가장 먼저 떠오르는 두 가지 강력한 패키지는 바로 firebase_messaging
과 flutter_local_notifications
입니다. firebase_messaging
은 Firebase Cloud Messaging(FCM)을 통해 원격 푸시 알림을 수신하는 데 사용되고, flutter_local_notifications
는 앱이 포그라운드에 있을 때 알림을 표시하거나, 특정 조건에 따라 기기 자체에서 로컬 알림을 생성하는 데 매우 유용합니다.
하지만 많은 개발자들이 이 두 패키지를 함께 사용하려 할 때 예상치 못한 문제에 부딪힙니다. 특히 iOS 환경에서 "FCM 메시지는 분명히 오는데 onMessage 콜백이 호출되지 않아요!", "알림을 탭해도 아무 반응이 없어요!" 와 같은 미스터리한 현상을 겪곤 합니다. 이 문제의 원인은 두 패키지가 iOS의 알림 처리를 담당하는 '대리자(Delegate)' 자리를 놓고 경쟁하기 때문입니다. 과거에는 이 문제를 해결하기 위해 flutter_local_notifications
패키지를 직접 fork하여 코드를 수정하는 복잡한 방법을 사용해야만 했습니다.
다행히도, 이제는 더 이상 그런 번거로운 과정을 거칠 필요가 없습니다. 최신 버전의 패키지들은 올바른 초기화 순서와 설정을 통해 두 기능을 완벽하게 조화시켜 사용할 수 있는 방법을 제공합니다. 이 글에서는 firebase_messaging
과 flutter_local_notifications
의 충돌 원인을 심도 있게 분석하고, 현재 가장 권장되는 최신 통합 방법을 단계별로 상세히 안내하여 여러분의 앱에 안정적이고 강력한 푸시 알림 시스템을 구축할 수 있도록 돕겠습니다. 더 이상 알림 충돌 문제로 시간을 낭비하지 마세요. 이 가이드를 통해 푸시 알림 기능을 완벽하게 정복해 보세요.
1. 왜 충돌이 발생할까? iOS 알림 처리 메커니즘의 이해
문제를 해결하기 위해선 먼저 원인을 알아야 합니다. iOS에서 푸시 알림이 어떻게 처리되는지, 그리고 두 패키지가 왜 서로 충돌하는지를 이해하는 것이 중요합니다.
iOS의 UNUserNotificationCenter와 Delegate 패턴
iOS 10부터 도입된 UserNotifications 프레임워크는 앱의 모든 알림(로컬 및 원격)을 관리하는 중심적인 역할을 합니다. 이 프레임워크의 핵심은 UNUserNotificationCenter
라는 싱글톤 객체입니다. 이 객체는 알림 권한을 요청하고, 알림을 예약하며, 수신된 알림을 처리하는 모든 작업을 총괄합니다.
여기서 중요한 개념이 바로 'Delegate(대리자)' 패턴입니다. UNUserNotificationCenter
는 알림과 관련된 특정 이벤트가 발생했을 때(예: 앱이 실행 중일 때 알림이 도착한 경우) 그 처리를 직접 하지 않고, 자신에게 등록된 '대리자' 객체에게 위임합니다. 이 대리자 객체는 UNUserNotificationCenterDelegate
프로토콜을 따라야 하며, 다음과 같은 중요한 메서드를 구현해야 합니다.
userNotificationCenter(_:willPresent:withCompletionHandler:)
: 앱이 포그라운드(Foreground) 상태에 있을 때 알림이 도착하면 호출됩니다. 이 메서드 안에서 알림을 사용자에게 어떻게 보여줄지(배너, 소리, 배지 등) 결정할 수 있습니다.userNotificationCenter(_:didReceive:withCompletionHandler:)
: 사용자가 알림을 탭했을 때, 또는 사일런트 푸시(Silent Push)가 도착했을 때 호출됩니다. 알림 클릭 후 특정 화면으로 이동하는 등의 로직을 처리하는 곳입니다.
핵심은 하나의 UNUserNotificationCenter
에는 단 하나의 대리자(delegate)만 등록될 수 있다는 점입니다.
두 패키지의 대리자 쟁탈전
이제 firebase_messaging
과 flutter_local_notifications
가 어떻게 동작하는지 봅시다.
firebase_messaging
의 역할: FCM으로부터 원격 푸시 알림을 수신하고, 이를 처리하기 위해 내부적으로UNUserNotificationCenter
의 대리자로 자신을 등록합니다. 그래야만onMessage
(포그라운드 수신)나 알림 탭 이벤트를 감지할 수 있기 때문입니다.flutter_local_notifications
의 역할: 이 패키지 역시 포그라운드에서 알림을 표시하거나, 사용자가 로컬 알림을 탭했을 때의 이벤트를 처리하기 위해UNUserNotificationCenter
의 대리자로 자신을 등록하려고 시도합니다.
문제는 여기서 발생합니다. 두 패키지를 별다른 설정 없이 함께 초기화하면, 나중에 초기화된 패키지가 먼저 등록된 대리자를 덮어쓰게 됩니다.
예를 들어, firebase_messaging
이 먼저 대리자를 등록했는데, 바로 뒤에 flutter_local_notifications
가 초기화되면서 UNUserNotificationCenter.current().delegate = self
와 같은 코드를 실행하면, 이제 알림 처리의 모든 권한은 flutter_local_notifications
에게 넘어갑니다. 그 결과, firebase_messaging
은 더 이상 포그라운드 알림 수신 이벤트나 알림 탭 이벤트를 받을 수 없게 되고, onMessage
콜백은 침묵하게 되는 것입니다. 이것이 바로 많은 개발자들이 겪는 충돌의 실체입니다.
과거에는 이 문제를 해결하기 위해 flutter_local_notifications
의 소스 코드에서 대리자를 설정하는 부분을 강제로 삭제하고 사용하는 방식(Fork & Modify)이 제안되었지만, 이는 패키지 업데이트를 어렵게 만들고 유지보수 비용을 증가시키는 매우 비효율적인 방법이었습니다.
2. 최신 접근법: 조화로운 통합 전략
현시점에서 이 문제를 해결하는 가장 올바르고 권장되는 방법은 '역할 분담과 협력'입니다. 즉, firebase_messaging
이 원격 알림 수신의 주도권을 계속 가지도록 하고, flutter_local_notifications
는 포그라운드에서 알림을 '보여주는' 역할에만 집중하도록 만드는 것입니다.
전체적인 흐름은 다음과 같습니다.
- FCM 설정:
firebase_messaging
을 주 알림 핸들러로 설정하고 초기화합니다. iOS에서 필요한 모든 권한을 요청합니다. - 포그라운드 알림 옵션 설정:
firebase_messaging
의setForegroundNotificationPresentationOptions
메서드를 사용하여 앱이 포그라운드에 있을 때 FCM 알림을 어떻게 처리할지 시스템에 알립니다. 기본적으로 포그라운드에서는 알림 배너가 뜨지 않으므로, 이 부분을 제어해야 합니다. - Local Notifications 초기화:
flutter_local_notifications
를 초기화합니다. 단, iOS 초기화 설정에서 대리자(delegate)를 설정하는 로직을 건드리지 않도록 주의합니다. 안드로이드를 위한 알림 채널 설정은 필수입니다. - 이벤트 연결:
firebase_messaging
의onMessage
콜백(포그라운드에서 FCM 수신 시 호출) 내부에서, 수신된 알림 정보를 바탕으로flutter_local_notifications
의 `show()` 메서드를 호출하여 사용자에게 로컬 알림을 '수동으로' 표시합니다.
이 방식을 사용하면 iOS의 알림 대리자는 firebase_messaging
이 계속 유지하므로 원격 알림 관련 콜백이 정상적으로 동작합니다. 동시에 포그라운드에서는 flutter_local_notifications
의 강력한 커스터마이징 기능을 활용하여 사용자에게 풍부한 알림 경험을 제공할 수 있습니다.
3. 단계별 통합 가이드: 실제 코드 구현하기
이제 이론을 바탕으로 실제 프로젝트에 어떻게 적용하는지 단계별로 살펴보겠습니다. 깔끔한 코드 관리를 위해 알림 관련 로직을 별도의 서비스 클래스로 분리하는 것을 추천합니다.
1단계: 프로젝트 설정 및 의존성 추가
먼저, pubspec.yaml
파일에 필요한 패키지들을 추가합니다.
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.27.0 # Firebase 핵심 기능
firebase_messaging: ^14.7.19 # FCM 기능
flutter_local_notifications: ^17.0.0 # 로컬 알림 기능
# 기타 필요한 패키지들...
터미널에서 flutter pub get
을 실행하여 패키지를 설치합니다.
2단계: Firebase 프로젝트 설정 및 플랫폼별 구성
이 단계는 기본적인 Firebase 설정 과정입니다. 이미 완료하셨다면 건너뛰어도 좋습니다.
- Firebase 프로젝트 생성: Firebase 콘솔에서 새 프로젝트를 생성합니다.
- 앱 등록: 프로젝트에 Android 및 iOS 앱을 등록합니다.
- Android:
android/app/build.gradle
파일에서applicationId
를 확인하여 입력하고, `google-services.json` 파일을 다운로드하여android/app/
디렉터리에 추가합니다. - iOS:
ios/Runner.xcworkspace
를 Xcode로 열어 Bundle Identifier를 확인하여 입력하고, `GoogleService-Info.plist` 파일을 다운로드하여 Xcode를 통해Runner/Runner
디렉터리에 추가합니다.
- Android:
- iOS 푸시 알림 기능 활성화: Xcode에서
Runner
타겟을 선택하고, 'Signing & Capabilities' 탭으로 이동합니다. '+ Capability'를 클릭하여 'Push Notifications'를 추가합니다. 또한, 'Background Modes'를 추가하고 'Remote notifications'를 체크합니다. - APNs 인증 키 설정: Apple Developer 계정에서 APNs(Apple Push Notification service) 인증 키(.p8 파일)를 생성하고, Firebase 콘솔의 '프로젝트 설정 > 클라우드 메시징 > Apple 앱 구성'에 업로드합니다. 이는 iOS 기기로 푸시를 보내기 위한 필수 과정입니다.
3단계: Android 매니페스트 및 설정
Android 8.0 (API 26) 이상에서는 알림을 표시하기 위해 '알림 채널(Notification Channel)'이 반드시 필요합니다. 또한, 기본 알림 아이콘과 색상을 지정해주는 것이 좋습니다.
android/app/src/main/AndroidManifest.xml
파일을 열고
태그 내부에 다음 메타데이터를 추가합니다.
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
ic_notification
아이콘은 일반적으로 하얀색의 간단한 로고 이미지를 사용하며, res/drawable
폴더에 위치해야 합니다. colorAccent
는 res/values/colors.xml
파일에 정의된 색상 리소스입니다.
4단계: 알림 서비스 클래스 작성 (핵심 로직)
이제 모든 설정이 완료되었으니, Dart 코드를 작성할 차례입니다. lib/services/notification_service.dart
와 같은 파일을 생성하고 아래 코드를 작성합니다.
import 'dart:convert';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
// 백그라운드/종료 상태에서 메시지를 처리하기 위한 최상위 함수
// @pragma('vm:entry-point') 어노테이션은 AOT 컴파일 시 이 함수가 트리 쉐이킹되지 않도록 보장합니다.
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// 백그라운드에서 어떤 작업을 수행해야 한다면 여기에 작성합니다.
// 예를 들어, API 호출, 로컬 데이터베이스 업데이트 등
// 여기서 UI 관련 작업은 수행할 수 없습니다.
await Firebase.initializeApp(); // 백그라운드에서도 Firebase 초기화가 필요할 수 있음
print("백그라운드 메시지 처리: ${message.messageId}");
}
class NotificationService {
// 싱글톤 패턴으로 인스턴스 관리
NotificationService._privateConstructor();
static final NotificationService _instance = NotificationService._privateConstructor();
factory NotificationService() {
return _instance;
}
// flutter_local_notifications 플러그인 인스턴스 생성
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
// Firebase Messaging 인스턴스 생성
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
// 알림 초기화를 위한 전체 메서드
Future<void> initialize() async {
// 1. FCM 초기 설정
await _initializeFCM();
// 2. Local Notifications 초기 설정
await _initializeLocalNotifications();
// 3. FCM 메시지 핸들러 설정
_setupMessageHandlers();
}
// 1. FCM 초기 설정
Future<void> _initializeFCM() async {
// iOS 포그라운드 알림 옵션 설정 - 이 부분이 중요!
// 이 설정을 통해 포그라운드에서도 알림(배너, 소리 등)이 보이도록 합니다.
// 하지만 우리는 Local Notifications를 통해 커스텀 UI를 보여줄 것이므로,
// 시스템 알림을 끌 수도 있습니다. 여기서는 기본 시스템 알림을 허용합니다.
await _firebaseMessaging.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads-up notification
badge: true,
sound: true,
);
// iOS 알림 권한 요청
if (Platform.isIOS) {
await _requestIOSPermissions();
}
// 디바이스 토큰 가져오기 (필요시 서버로 전송)
final fcmToken = await _firebaseMessaging.getToken();
print("FCM Token: $fcmToken");
// 토큰 갱신 감지
_firebaseMessaging.onTokenRefresh.listen((newToken) {
print("FCM Token is refreshed: $newToken");
// 여기서 새로운 토큰을 서버로 전송하는 로직을 구현합니다.
});
}
// iOS 권한 요청
Future<void> _requestIOSPermissions() async {
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
}
// 2. Local Notifications 초기 설정
Future<void> _initializeLocalNotifications() async {
// Android 초기화 설정
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher'); // 앱 아이콘 사용
// iOS 초기화 설정
// onDidReceiveLocalNotification은 오래된 iOS 버전을 위한 콜백입니다.
// **가장 중요한 부분**: `defaultPresentAlert`, `defaultPresentBadge`, `defaultPresentSound`를 false로 설정하여
// Local Notifications 플러그인이 자동으로 포그라운드 알림을 표시하지 않도록 합니다.
// 우리는 FCM의 onMessage에서 수동으로 알림을 표시할 것이기 때문입니다.
// 이것이 바로 과거의 Delegate 충돌을 피하는 핵심적인 현대적 방법입니다.
final DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
// 알림 플러그인 초기화 및 콜백 설정
await _localNotifications.initialize(
initializationSettings,
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
onDidReceiveBackgroundNotificationResponse: onDidReceiveBackgroundNotificationResponse,
);
}
// 3. FCM 메시지 핸들러 설정
void _setupMessageHandlers() {
// 앱이 포그라운드에 있을 때 메시지 처리
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
// **핵심 로직**: FCM 메시지를 받으면 Local Notification을 사용하여 알림을 표시합니다.
// 이를 통해 일관된 알림 경험을 제공하고, 커스터마이징이 가능해집니다.
showLocalNotification(message);
}
});
// 앱이 백그라운드 또는 종료 상태에 있을 때 메시지 처리
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 앱이 종료된 상태에서 알림을 탭하여 열었을 때 메시지 가져오기
_firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
print("앱 종료 상태에서 알림 탭: ${message.data}");
// 여기서 payload를 기반으로 특정 페이지로 네비게이션하는 로직을 추가할 수 있습니다.
// 예: navigatorKey.currentState?.pushNamed('/details', arguments: message.data);
}
});
// 앱이 백그라운드 상태에서 알림을 탭하여 열었을 때 메시지 처리
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('A new onMessageOpenedApp event was published!');
print("앱 백그라운드 상태에서 알림 탭: ${message.data}");
// 여기서 payload를 기반으로 특정 페이지로 네비게이션하는 로직을 추가할 수 있습니다.
});
}
// Local Notification을 실제로 표시하는 함수
Future<void> showLocalNotification(RemoteMessage message) async {
final remoteNotification = message.notification;
final android = message.notification?.android;
// Android용 알림 채널 생성 (매번 호출해도 이미 생성되어 있으면 무시됨)
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // ID
'High Importance Notifications', // 제목
description: 'This channel is used for important notifications.', // 설명
importance: Importance.max,
);
await _localNotifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
if (remoteNotification != null) {
_localNotifications.show(
remoteNotification.hashCode, // 알림 ID
remoteNotification.title, // 알림 제목
remoteNotification.body, // 알림 본문
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
icon: android?.smallIcon ?? '@mipmap/ic_launcher',
// 기타 안드로이드 전용 설정
),
iOS: const DarwinNotificationDetails(
badgeNumber: 1, // 배지 숫자 등 iOS 전용 설정
),
),
payload: jsonEncode(message.data), // 알림 탭 시 전달할 데이터
);
}
}
// --- Local Notifications 콜백 함수들 ---
// (오래된 iOS 버전용) 로컬 알림 수신 시 콜백
void onDidReceiveLocalNotification(int id, String? title, String? body, String? payload) {
print('onDidReceiveLocalNotification: id-$id, title-$title, payload-$payload');
}
// (최신 버전) 알림 탭 시 호출되는 콜백 (앱이 포그라운드/백그라운드일 때)
void onDidReceiveNotificationResponse(NotificationResponse notificationResponse) {
final String? payload = notificationResponse.payload;
if (payload != null) {
print('notification payload: $payload');
final data = jsonDecode(payload);
// 여기서 payload 데이터를 사용하여 특정 페이지로 이동하는 로직을 구현합니다.
// 예: if (data['type'] == 'chat') { navigatorKey.currentState?.push(...) }
}
}
}
// (최신 버전) 앱이 종료된 상태에서 알림 탭 시 호출되는 콜백
@pragma('vm:entry-point')
void onDidReceiveBackgroundNotificationResponse(NotificationResponse notificationResponse) {
print('onDidReceiveBackgroundNotificationResponse: payload - ${notificationResponse.payload}');
// 이 콜백은 앱이 완전히 종료된 상태에서 알림을 탭했을 때 호출됩니다.
// main 함수에서 초기화 시 이 콜백이 실행되었는지 확인하고 데이터를 처리할 수 있습니다.
}
5단계: main.dart에서 서비스 초기화
마지막으로, 앱이 시작될 때 위에서 만든 NotificationService
를 초기화해야 합니다. main.dart
파일을 다음과 같이 수정합니다.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; // flutterfire configure를 통해 생성된 파일
import 'services/notification_service.dart'; // 방금 만든 서비스 파일
void main() async {
// Flutter 엔진과 위젯 바인딩 초기화
WidgetsFlutterBinding.ensureInitialized();
// Firebase 초기화
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 알림 서비스 초기화
await NotificationService().initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Notification Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FCM & Local Notifications'),
),
body: const Center(
child: Text('푸시 알림을 기다리는 중...'),
),
);
}
}
이제 모든 준비가 끝났습니다. 앱을 실행하고 Firebase 콘솔이나 여러분의 백엔드 서버를 통해 FCM 메시지를 보내보세요. 앱이 포그라운드 상태일 때도 flutter_local_notifications
에 의해 생성된 아름다운 알림이 표시되고, 알림을 탭했을 때 onDidReceiveNotificationResponse
콜백이 정상적으로 호출되는 것을 확인할 수 있을 것입니다.
4. 심화 주제 및 문제 해결
알림 페이로드(Payload)와 화면 이동
실제 앱에서는 알림을 탭했을 때 특정 화면으로 이동하는 기능이 필수적입니다. 이는 알림의 `payload`를 통해 구현할 수 있습니다.
- FCM 메시지 보낼 때: `data` 필드에 화면 이동에 필요한 정보를 담아 보냅니다.
{ "to": "FCM_TOKEN", "notification": { "title": "새로운 메시지", "body": "친구가 메시지를 보냈습니다." }, "data": { "type": "chat", "screen": "/chat_room", "chat_room_id": "12345" } }
- Flutter에서 처리: `NotificationService`의 `onDidReceiveNotificationResponse` 콜백에서 payload를 파싱하여 처리합니다.
// main.dart에 NavigatorKey를 전역으로 선언 final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // onDidReceiveNotificationResponse 내부 void onDidReceiveNotificationResponse(NotificationResponse notificationResponse) { final String? payload = notificationResponse.payload; if (payload != null) { final data = jsonDecode(payload); if (data['screen'] != null) { navigatorKey.currentState?.pushNamed(data['screen'], arguments: data); } } }
이 방법을 사용하려면
MaterialApp
에 `navigatorKey`를 설정하고, `onGenerateRoute`를 통해 라우팅 로직을 구현해야 합니다.
백그라운드/종료 상태에서의 데이터 처리
FirebaseMessaging.onBackgroundMessage
핸들러는 UI 컨텍스트와 격리된 별도의 Isolate에서 실행됩니다. 따라서 이 함수 내에서는 setState
와 같은 UI 업데이트나 네비게이션 작업을 직접 수행할 수 없습니다.
- 허용되는 작업: HTTP 요청, 로컬 스토리지(SharedPreferences, Hive 등)에 데이터 저장, 다른 플러그인과의 상호작용(메서드 채널을 통해) 등.
- 주의사항: 이 핸들러는 반드시 최상위(top-level) 함수이거나 static 메서드여야 합니다. 클래스 내부의 인스턴스 메서드는 될 수 없습니다.
자주 겪는 문제와 해결책 (Troubleshooting)
- 문제: 안드로이드에서 알림이 아예 오지 않아요.
- 해결책 1:
AndroidManifest.xml
에com.google.firebase.messaging.default_notification_channel_id
메타데이터가 올바르게 설정되었는지 확인하세요. - 해결책 2:
NotificationService
에서AndroidNotificationChannel
을 생성하고 `createNotificationChannel`을 호출하는 로직이 포함되어 있는지 확인하세요. Android 8 이상에서는 채널이 없으면 알림이 표시되지 않습니다. - 해결책 3: 기기의 앱 설정에서 알림 권한이 활성화되어 있는지 확인하세요.
- 해결책 1:
- 문제: iOS 시뮬레이터에서 푸시 알림이 오지 않아요.
- 해결책: iOS 시뮬레이터는 원격 푸시 알림을 지원하지 않습니다. APNs를 통한 푸시 알림 테스트는 반드시 실제 iOS 기기에서 진행해야 합니다.
- 문제:
onMessage
는 호출되는데, 포그라운드에서 알림 배너가 보이지 않아요.- 해결책 1:
firebase_messaging.setForegroundNotificationPresentationOptions
가 호출되었는지, `alert: true` 옵션이 포함되었는지 확인하세요. - 해결책 2: `onMessage` 리스너 내부에서
flutter_local_notifications.show()
메서드를 호출하여 수동으로 알림을 표시하는 로직이 제대로 구현되었는지 확인하세요.
- 해결책 1:
결론: 이제 충돌은 없다
Flutter에서 firebase_messaging
과 flutter_local_notifications
를 함께 사용하는 것은 더 이상 골치 아픈 문제가 아닙니다. 과거의 충돌은 iOS의 알림 처리 Delegate를 두 패키지가 서로 차지하려 했기 때문에 발생했지만, 이제는 명확한 역할 분담을 통해 두 패키지의 장점만을 취할 수 있습니다.
핵심을 다시 정리하면 다음과 같습니다.
- 주도권은
firebase_messaging
에게: FCM이 원격 알림 수신과 관련된 모든 이벤트를 주도적으로 처리하도록 합니다. flutter_local_notifications
는 조력자: 포그라운드 상태에서 FCM 메시지를 수신했을 때, 이 패키지를 사용하여 사용자에게 시각적인 알림을 '보여주는' 도구로 활용합니다.- 올바른 초기화가 관건: 두 패키지를 초기화할 때, 특히
flutter_local_notifications
의 iOS 설정을 조정하여 Delegate 충돌을 원천적으로 방지합니다.
이 가이드에서 제시한 단계별 통합 방법과 코드 예제를 따르면, 여러분의 Flutter 앱에 안정적이고 확장 가능한 푸시 알림 시스템을 성공적으로 구축할 수 있을 것입니다. 이제 알림 충돌의 미스터리에서 벗어나, 사용자에게 더 나은 경험을 제공하는 데 집중하시기 바랍니다.
0 개의 댓글:
Post a Comment