구현 목표
사용자가 알림을 클릭했을 때, 지정된 웹 링크로 이동하는 기능을 구현한다.
FCM 메시지 수신 시 기기 상태
| 상태 | 설명 |
| Foreground | 앱이 켜져 있고 사용자에게 보여지고 있는 상태 |
| Background | 앱은 실행 중이지만 백그라운드로 전환된 상태 (예: 홈 버튼 누름) |
| Terminated | 앱이 완전히 종료된 상태 |
각 상태에 따라 알림 수신 및 처리 방식이 다르므로, 상태에 맞는 코드를 구현해야 한다.
기본 설정
기본적인 FCM 연동 방법은 다음 글을 참고한다.
[Flutter] Android에서 FCM 연동(1) - 푸시 알림 수신 테스트
flutter pub add url_launcher
`url_launcher` 패키지를 설치한다.
import 'package:url_launcher/url_launcher.dart';
Future<void> openUrlInExternalApp(Uri url) async {
if (!await launchUrl(
url,
mode: LaunchMode.externalApplication,
)) {
throw Exception('Could not launch $url');
}
}
`url_launcher` 패키지를 이용해서 링크를 실행하는 유틸 함수를 만들었다.
{
"notification": {
"title": "새 소식!",
"body": "지금 확인해보세요!"
},
"data": {
"link": "https://www.google.com/"
}
}
다음과 같은 구조로 메시지를 전송했다고 가정하고 설명한다.
Foreground 상태
Flutter는 포그라운드 상태에서 FCM 알림을 자동으로 띄우지 않기 때문에,
`flutter_local_notifications` 패키지를 이용해서 로컬 알림을 통해 사용자에게 보여줘야 한다.
초기화 및 수신 처리
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> _initLocalNotification() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
// 포그라운드 알림 클릭 시 링크로 이동
onDidReceiveNotificationResponse: (NotificationResponse response) {
final link = response.payload;
if (link != null && link.isNotEmpty) {
openUrlInExternalApp(Uri.parse(link));
}
},
);
}
알림 표시
Future<void> _showNotification(RemoteMessage message) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'fcm_default_channel', // 채널 ID
'FCM 알림', // 채널 이름
importance: Importance.max,
priority: Priority.high,
);
await flutterLocalNotificationsPlugin.show(
0,
message.notification?.title ?? '알림',
message.notification?.body ?? '메시지가 도착했습니다.',
NotificationDetails(android: androidNotificationDetails);
payload: message.data['link'], // 클릭 시 사용할 링크
);
}
포그라운드 수신 리스너
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
_showNotification(message);
}
});
Background 상태
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final link = message.data['link'];
if (link != null) {
openUrlInExternalApp(
Uri.parse(link),
);
}
});
알림 클릭 시 Firebase가 자동으로 앱을 열고, `onMessageOpenedApp`에서 메시지를 전달받는다.
Terminated 상태
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
final link = initialMessage.data['link'];
if (link != null) {
openUrlInExternalApp(Uri.parse(link));
}
}
앱이 완전히 종료된 상태에서 알림을 클릭한 경우, `getInitialMessage()`로 메시지를 가져와야 한다.
전체 코드
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'firebase_options.dart';
import 'utils/launch_utils.dart';
import 'screens/root_screen.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// 로컬 알림 초기화
Future<void> _initLocalNotification() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
// 포그라운드 알림 클릭 시 링크로 이동
onDidReceiveNotificationResponse: (NotificationResponse response) {
final link = response.payload;
if (link != null && link.isNotEmpty) {
openUrlInExternalApp(Uri.parse(link));
}
},
);
}
// 포그라운드 알림 표시
Future<void> _showNotification(RemoteMessage message) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'fcm_default_channel', // 채널 ID
'FCM 알림', // 채널 이름
channelDescription: '포그라운드 FCM 알림',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
);
const NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.show(
0,
message.notification?.title ?? '알림',
message.notification?.body ?? '메시지가 도착했습니다.',
notificationDetails,
payload: message.data['link'], // 클릭 시 사용할 링크
);
}
// 백그라운드 알림 리스너
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("Handling a background message: ${message.messageId}");
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 앱이 종료된 상태에서 알림 클릭하는 경우 대비
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
final link = initialMessage.data['link'];
if (link != null) {
openUrlInExternalApp(Uri.parse(link));
}
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? _token = '';
@override
void initState() {
super.initState();
_initLocalNotification();
_initFCM();
}
void _initFCM() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
messaging.subscribeToTopic('all'); // FCM 모든 사용자의 토큰 구독
// 권한 요청 (Android 13 이상)
NotificationSettings settings = await messaging.requestPermission();
print('User granted permission: ${settings.authorizationStatus}');
// 포그라운드 알림 리스너
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}');
_showNotification(message);
}
});
// 알림 클릭 시 링크로 이동
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final link = message.data['link'];
if (link != null) {
openUrlInExternalApp(
Uri.parse(link),
);
}
});
// 토큰 확인
String? token = await messaging.getToken();
print('token: $token');
setState(() {
_token = token;
});
}
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Title',
debugShowCheckedModeBanner: false,
home: RootScreen(),
);
}
}
구현 결과

플러터 FCM 연동 시리즈