우당탕탕

Flutter 푸시 알림 FCM 연동, 2026년 꼭 알아야 할 변경점과 경험 본문

Tech/Flutter

Flutter 푸시 알림 FCM 연동, 2026년 꼭 알아야 할 변경점과 경험

모찌모찝 2026. 6. 21. 19:07

Flutter에서 FCM(Firebase Cloud Messaging) 푸시 알림 연동을 구현하다가 생각보다 삽질을 많이 했어요. 특히 2026년 들어서 구글 Firebase SDK와 Flutter 플러그인 쪽에서 뭔가 많이 달라져서, 예전 방식으로 세팅하다 막힌 경우가 한두 번이 아니었거든요.

이 글에서는 제가 직접 겪은 FCM 연동 과정에서의 문제점과 2026년 달라진 부분, 그리고 이를 어떻게 해결했는지 꼼꼼히 공유하려고 해요. 특히 기존 공식문서나 예전 튜토리얼에 의존했다가 막히시는 분들에게 큰 도움이 될 거예요.

개발 환경 / 버전 정보

저는 Flutter 3.13.0, Firebase Core 2.12.0, FlutterFire Messaging 14.0.1 버전을 사용했어요. 그리고 Firebase Console에서 Cloud Messaging API는 2026년에 업데이트된 정책을 반영했구요.

참고로 Android는 SDK 버전 33(즉, Android 13) 이상, iOS는 Xcode 15 환경에서 작업했는데, 여기서도 달라진 권한 처리 부분 때문에 한참 헤맸던 경험이 있습니다.

푸시 알림 권한 요청, 2026년엔 이렇게 달라졌어요

사실 이 부분이 제일 골치 아팠는데요, 2026년부터 iOS, 특히 iOS 16.5 이상에서는 Firebase Messaging 플러그인의 권한 요청 방식이 바뀌었어요. 기존에는 단순히 requestPermission() 메서드 호출만으로 모든 권한을 얻었지만, 지금은 권한 타입별로 세분화되어 하나씩 요청해야 해요.

그리고 Android 13(API 33) 이상부터는 푸시 알림도 런타임 권한 요청을 해야 하기 때문에 AndroidManifest.xml에만 권한을 명시하는 게 아니라 실제 앱 실행 중에 권한 요청 팝업을 띄워줘야 하더라고요.

// iOS와 Android 권한 요청을 분리해서 처리하는 예시

import 'package:firebase_messaging/firebase_messaging.dart';
import 'dart:io';

Future requestNotificationPermission() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;

  if (Platform.isIOS) {
    NotificationSettings settings = await messaging.requestPermission(
      alert: true,
      announcement: false, // 2026년부터 추가된 옵션
      badge: true,
      carPlay: false,
      criticalAlert: true, // 중요 알람 권한
      provisional: false,
      sound: true,
    );
    print('iOS 권한 상태: ");
  } else if (Platform.isAndroid) {
    // Android 13 이상에서 런타임 푸시 권한 요청
    // 플러그인에 따라 다르지만 아래처럼 구현해야 함
    final status = await messaging.requestPermission();
    print('Android 권한 상태: $status');
  }
}

이 부분을 안 해주면 iOS에서 알림이 아예 안 오거나, Android에서 앱 실행 초반에 푸시 알림 자체가 차단되는 거로 보여서 한참 고생했어요.

Firebase 초기화와 메시지 수신 설정 이렇게 했어요

그런데 여기서 많이 헷갈려 하시는 게, Firebase 초기화 시점을 어디에 두느냐더라고요. 2026년 공식 문서에도 초기화는 앱 시작 시 한 번만 하라고 명시되어 있는데, 최신 FlutterFire 플러그인들은 firebase_core 라이브러리와 연동하여 비동기로 초기화를 기다려야 하는 구조가 있어서 다음처럼 작성했습니다.

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // 앱 시작 전에 꼭 초기화 완료 기다림
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FCM Demo',
      home: PushNotificationPage(),
    );
  }
}

그리고 메시지 수신은 FirebaseMessaging 인스턴스를 통해 아래처럼 백그라운드, 포그라운드 메시지를 각각 설정해 줬는데요, 2026년 기준으로는 메시지 핸들러 함수 선언 방식에 살짝 변화가 있더라고요.

// 백그라운드 메시지 처리
Future firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('백그라운드에서 메시지 수신: ");
}

// 앱 시작 시
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

// 포그라운드 메시지
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('포그라운드에서 메시지: ");
  // 여기서 알림 표시 로직 추가 가능
});

이전에는 백그라운드 핸들러 등록이 선택적이었는데, 2026년에는 이게 없으면 알림이 제대로 안 오거나 앱 크래시가 날 수도 있어서 꼭 넣어 주어야 했어요.

여기서 삽질했던 부분들

실제로 막혔던 에러와 해결 과정을 공유할게요. 이 에러가 왜 나는지 한참 찾았는데, Firebase SDK에서 2026년부터 권한 요청 API가 완전히 변경됐다는 걸 몰라서 전혀 엉뚱한 코드만 계속 쓰고 있었어요.

platformException(ERROR_MISSING_PERMISSION, Missing App ID, null, null)

이 에러는 Firebase 설정 파일인 GoogleService-Info.plist(iOS)나 google-services.json(Android)을 제대로 프로젝트에 추가하지 않았거나, Firebase Console에서 앱 등록 단계를 제대로 안 끝냈을 때도 흔히 발생하는데, 저는 권한 요청 자체가 바뀌면서 SDK 초기화가 꼬인 탓이 큽니다.

또 하나는 Android에서 런타임 권한이 안 떠서 푸시가 안 오는 현상인데, AndroidManifest.xml에 아래처럼 명시해줘야 하는 것도 잊으면 안 돼요.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

권한 팝업은 직접 Flutter 코드에서 요청을 해야 하니, 그냥 매니페스트만 넣는다고 되는 게 아니라 폰에서 직접 사용자 허락을 받아야 한다는 점도 꽤 헷갈리더라고요.

이렇게 하면 FCM 토큰 가져오기와 알림 클릭 이벤트도 잘 됩니다

저는 토큰이 제대로 생성됐는지 확인하려고 아래 코드를 썼는데요, 2026년 버전에서는 꼭 권한 요청 이후에 이 코드를 호출해야 안정적으로 토큰을 받을 수 있어요.

final FirebaseMessaging messaging = FirebaseMessaging.instance;

Future getToken() async {
  String? token = await messaging.getToken();
  print('FCM 토큰: $token');
}

그리고 알림을 눌러서 앱이 실행됐을 때 어떤 데이터를 받아오는지 확인하는 것도 필수인데, 아래처럼 메시지의 data 혹은 notification 오브젝트가 잘 전달되는지 꼭 체크하세요.

// 앱이 완전히 종료된 상태에서 알림 클릭 시
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
  if (message != null) {
    print('앱 종료 상태에서 알림 열림: ${message.data}');
  }
});

// 앱 포그라운드나 백그라운드에서 알림 클릭 시
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  print('알림 눌렀을 때 실행: ${message.data}');
});

자주 물어보시는 것들

Q. 2025년에 작성한 Flutter FCM 연동 코드 그대로 써도 되나요?

A. 안 되어요. 특히 권한 요청 방식과 백그라운드 메시지 처리 함수 등록이 많이 바뀌었어요. 예전 방식으로 그냥 두면 알림이 오지 않거나 앱이 크래시 날 위험이 큽니다.

Q. Android 12 이하 버전에서도 권한 요청을 해야 할까요?

A. 권한 팝업은 Android 13 이상에서만 필수입니다. 그 아래 버전은 매니페스트에 명시만 해도 되니까, 조건부 코드로 분기하는 게 좋아요.

Q. 토큰이 계속 바뀌는데 어디서 관리해야 하나요?

A. FirebaseMessaging.onTokenRefresh 스트림을 구독해서 주기적으로 서버에 업데이트하는 게 안전합니다.

최근 Firebase 콘솔에서 토큰 만료 정책과 보안 향상을 위해 이런 조치가 강화됐다고 하니까 참고하세요.

Flutter FCM 연동이 2026년에 이렇게 많이 바뀌는 줄 몰랐다면 저처럼 삽질만 계속했을 텐데, 차근차근 권한 요청부터 백그라운드 메시지 처리까지 최신 가이드에 맞춰 진행하면 훨씬 매끄럽게 구현할 수 있으니 꼭 확인해 보세요.

Comments