티스토리 뷰
Developments/Flutter
[Flutter] FirebaseCloudMessage Foreground, Background 메세지 보내기 삽질 기록
doyeonjeong_ 2025. 5. 21. 11:13최근 이틀 동안 iOS, 안드로이드 환경에서 동일하게 푸시 알림 설정을 보내려고 삽질을 했다...
그러다 마주친 4주 전에 올라온 이 강의를 보고 해결되었다...ㅠㅠ
같은 어려움을 겪는 사람들을 위해서 간단히 기록을 남겨본다...🫠
(참고로 APNs 설정은 절차가 복잡해서 직접 강의를 보는 걸 추천)
반말체로 갑니다~~~~!
📌 준비
아래의 파일 4가지를 수정하거나 추가할 예정임.
- iOS 폴더 내
AppDelegate.swift
- lib 폴더 내
🔸 AppDelegate.swift
iOS의 푸시 알림 권한 및 delegate 설정을 여기서 하게 됨
느낌적으로 AppDelegate의 호출순서와 import 구문이 중요
// AppDelegate.swift
import UIKit
import Flutter
import flutter_local_notifications
@main
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
GeneratedPluginRegistrant.register(with: registry)
}
// 2
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
// 3
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
🔸 firebase_messaging_service.dart
싱글톤 패턴으로 작성됨.
Firebase 푸시 메시지를 수신하면 이를 로컬 알림 서비스로 넘겨서 사용자에게 즉시 표시하는 역할을 수행.
- init(): Firebase 메시지 수신을 위한 기본 설정 (알림 권한 요청, 메시지 리스너 설정 등)
- onMessage(): Foreground 상태에서 메시지를 수신했을 때 로컬 알림으로 변환하여 즉시 표시
- onMessageOpenedApp(): 사용자가 푸시 알림을 눌러 앱을 열었을 때 실행되는 로직 처리
미래의 제가 볼까봐 주석이 좀 많아요...😂
// services/firebase_messaging_service.dart
class FirebaseMessagingService {
// 싱글 톤 패턴 - private 생성자
FirebaseMessagingService._internal();
// 싱글톤 인스턴스
static final FirebaseMessagingService _instance =
FirebaseMessagingService._internal();
// 싱글톤 인스턴스를 제공하는 팩토리 생성자
factory FirebaseMessagingService.instance() => _instance;
// 알림 표시를위한 로컬 알림 서비스에 대한 참조
LocalNotificationsService? _localNotificationsService;
/// Firebase 메시징을 초기화하고 모든 메시지 리스너를 설정합니다
Future<void> init(
{required LocalNotificationsService localNotificationsService}) async {
_localNotificationsService = localNotificationsService;
// FCM 토큰 처리
_handlePushNotificationsToken();
// 알림 권한 요청
_requestPermission();
// 백그라운드 메시지 처리 등록 (앱 종료)
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 앱이 포어그라운드에 있을 때 메시지 수신
FirebaseMessaging.onMessage.listen(_onForegroundMessage);
// 앱이 백그라운드에 있지만 종료되지 않을 때 알림 탭 수신
FirebaseMessaging.onMessageOpenedApp.listen(_onMessageOpenedApp);
// 종료된 상태에서 앱을 열었을 때 초기 메시지 확인
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_onMessageOpenedApp(initialMessage);
}
}
/// 푸시 알림을 위해 FCM 토큰을 검색하고 관리합니다
Future<void> _handlePushNotificationsToken() async {
// 푸시 알림을 위한 FCM 토큰 검색
final token = await FirebaseMessaging.instance.getToken();
debugPrint(' ~~~ 🔑 ~~~ FCM 토큰 : $token');
FirebaseMessaging.instance.onTokenRefresh.listen((token) {
debugPrint(' ~~~ 🔑 ~~~ 토큰 갱신 : $token');
// 타겟팅을 위해 토큰을 서버에 보내는 로직 추가해야함
}).onError((error) {
debugPrint(' ~~~ 🐛 ~~~ 토큰 갱신 오류 : $error');
});
}
/// 사용자에게 알림 권한 요청
Future<void> _requestPermission() async {
// 경고, 배지, 소리에 대한 권한 요청
final result = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
// 사용자의 권한 결정 로그
debugPrint(' ~~~ 📣 ~~~ 알림 권한 요청 : ${result.authorizationStatus}');
}
/// 앱이 포어그라운드에 있을 때 수신된 메시지 처리
void _onForegroundMessage(RemoteMessage message) {
debugPrint(' ~~~ 📣 ~~~ 포그라운드 알림 수신 : ${message.data.toString()}');
final notificationData = message.notification;
if (notificationData != null) {
// 서비스를 사용하여 로컬 알림 표시
_localNotificationsService?.showNotification(
notificationData.title,
notificationData.body,
message.data.toString(),
);
}
}
/// 앱이 백그라운드 또는 종료된 상태에서 열리면 알림 탭 처리
void _onMessageOpenedApp(RemoteMessage message) {
debugPrint(
' ~~~ 📣 ~~~ 백그라운드 알림 수신 : ${message.data.toString()}');
// 메시지 데이터에 따라 탐색 또는 특정 처리 추가해야함
}
}
/// 백그라운드 메시지 처리기 (반드시 최상위 함수 또는 정적 함수여야 함)
/// 앱이 완전히 종료된 상태에서 메시지 처리
/// @pragma('vm:entry-point') 란?
/// 안드로이드 네이티브 코드에서 다트 함수를 실행할 수 있게 해주는 구문
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
debugPrint(' ~~~ 📣 ~~~ 백그라운드 알림 수신 : ${message.data.toString()}');
}
🔸 local_notifications_service.dart
싱글톤 패턴으로 작성됨.
앱이 Foreground 상태일 때 로컬 알림을 실제로 표시하는 로직을 처리함.
- init(): 로컬 알림 채널 및 기본 알림 설정 초기화
- showNotification(): 전달받은 메시지 데이터를 실제 로컬 알림으로 사용자에게 보여줌
// services/local_notifications_service.dart
class LocalNotificationsService {
// 싱글톤 패턴을 위한 private 생성자
LocalNotificationsService._internal();
//싱글톤 인스턴스
static final LocalNotificationsService _instance =
LocalNotificationsService._internal();
//싱글톤 인스턴스를 반환하는 팩토리 생성자
factory LocalNotificationsService.instance() => _instance;
// 알림 처리를위한 메인 플러그인 인스턴스
late FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin;
// 앱 런처 아이콘을 사용하는 Android-specific 초기화 설정
final _androidInitializationSettings =
const AndroidInitializationSettings('@mipmap/ic_launcher');
// 권한 요청이있는 iOS 별 초기화 설정
final _iosInitializationSettings = const DarwinInitializationSettings();
// Android 알림 채널 구성
final _androidChannel = const AndroidNotificationChannel(
'channel_id',
'Channel name',
description: 'Android push notification channel',
importance: Importance.max,
);
// 초기화 상태 추적을 위한 플래그
bool _isFlutterLocalNotificationInitialized = false;
// 고유 알림 ID 생성을 위한 카운터
int _notificationIdCounter = 0;
/// Android 및 iOS에 대한 로컬 알림 플러그인 초기화
Future<void> init() async {
// 이미 초기화되었는지 확인하여 중복 설정 방지
if (_isFlutterLocalNotificationInitialized) {
return;
}
// 플러그인 인스턴스 생성
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// 플랫폼별 설정 결합
final initializationSettings = InitializationSettings(
android: _androidInitializationSettings,
iOS: _iosInitializationSettings,
);
// 설정 및 알림 탭 수신 콜백을 사용하여 플러그인 초기화
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
debugPrint('~~~ ✅ ~~~ 포그라운드 알림 탭: ${response.payload}');
},
);
// Android 알림 채널 생성
await _flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(_androidChannel);
_isFlutterLocalNotificationInitialized = true;
debugPrint('~~~ ✅ ~~~ 로컬 알림 서비스가 초기화');
}
/// 로컬 알림 표시
Future<void> showNotification(
String? title,
String? body,
String? payload,
) async {
AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
_androidChannel.id,
_androidChannel.name,
channelDescription: _androidChannel.description,
importance: Importance.max,
priority: Priority.high,
);
const iosDetails = DarwinNotificationDetails();
final notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _flutterLocalNotificationsPlugin.show(
_notificationIdCounter++,
title,
body,
notificationDetails,
payload: payload,
);
}
}
🔸 main.dart
main.dart
의 초기화 순서가 가장 중요함. 아래와 같이 초기화 로직을 짜면 문제없이 동작한다.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DotEnvService.load();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
final localNotificationsService = LocalNotificationsService.instance();
await localNotificationsService.init();
final firebaseMessagingService = FirebaseMessagingService.instance();
await firebaseMessagingService.init(
localNotificationsService: localNotificationsService,
);
runApp(const App());
}
📌 마무리
확실히 푸시 알림 설정(APNs 관련 포함)은 막막하고 어려웠지만, 정확한 호출 순서와 각 서비스의 역할을 이해하니 해결이 가능했음.
비슷한 문제로 힘들어하는 사람이 있다면 위의 코드를 꼭 참고하면 좋겠다.
이 글이 삽질 시간을 단축하는 데 조금이라도 도움이 되기를 바라며! 😊
'Developments > Flutter' 카테고리의 다른 글
[Flutter/Android] Failed host lookup 오류 해결 (feat. 옆집 iOS는 잘되는데...) (1) | 2025.05.12 |
---|---|
[Flutter] Widget Lifecycle 이해하기: Stateful vs Stateless (0) | 2025.03.13 |
Flutter Lints 와 analysis_options (0) | 2025.03.07 |
Dart의 생성자를 ㅇrㄹr보좌.. (0) | 2025.03.05 |
[Flutter] 3.7 업데이트 살펴보기 (3) (0) | 2025.02.14 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 오블완챌린지
- sliverlist
- swiftmigration
- fromjson()
- ios
- jsonserializable
- 낙관적 업데이트 패턴
- jsonkey
- flutter 3.7
- 티스토리챌린지
- flutter updates
- SWIFT
- lints
- tojson()
- sliver
- flutter_lints
- 개발신입
- 플러터네이티브
- llm 설치
- 플러터ios애니메이션
- flutter3.7
- analysis_options
- 오블완
- 플러터 업데이트
- slivers
- 플러터
- 다트문법
- 렌더링최적화
- flutter
- 플러터업데이트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
글 보관함