Flutter: FCM 푸쉬 알림

Notification눌른 다음 다른 화면으로 Push하려면?

Soo Kim
9 min readAug 19, 2022

이번 포스트 에서는 제가 어떻게 FCM 푸쉬 알림 기능을 썼는지 설명하겠습니다. 하루 조금 해매다가 잘 작동하는것 같아서, 다른 분들도 도움이 될까 해서 올립니다.

https://firebase.google.com/docs/cloud-messaging

iOS Setup

Apple…참 복잡하고, 까다롭고, 나를 귀찮게 하는 애플입니다. 참고로 이 기능을 쓸려면 실제 iOS 기기가 필요하며, Apple Developer Program에 Admin 또는 Account Holder으로 가입 되어있어야 됩니다.

  1. XCode에서 Targets — Runner — Signing & Capabilities에서 + 를 클릭하여 Push Notification을 추가합니다. Background Mode도 추가하여 Background fetch 와 Remote notification을 추가합니다.
XCode

2. Apple Developer Member Center — Certificates, Identifiers & Profile를 누르고, Key 탭 에서 Apple Push Notification service (APN)의 key를 추가합니다. 그리고 Firebase Console — Project Settings — Cloud Messaging — Apple app configuration에 추가를 합니다.

3. 추가적으로 더 자세한 설명은 official document 참고 바랍니다.

Android Setup

Foreground Notification (잠깐 핸드폰 상단에 디스플레이 되는 notification)을 쓰기위해서는 AndroidManifest.xml에 아래와 같이 meta-data가 필요합니다. “high_importance_channel”은 그냥 Firebase official document에 있는 거 사용했으며, 밑에서 나올 platformChannelSpecifics에서 마음대로 설정할 수 있습니다.

<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />

Initialize Firebase

일단 필요한 패키지들을 다 설치합니다.

flutter pub add firebase_messaging
flutter pub add firebase_core
flutter pub add flutter_local_notifications

저는 provider와 service파일 나누는게 익숙해서 나눴지만, 굳이 안 나눠도 됩니다. Firebase는 처음 main 메소드에서 실행해줘야 됩니다.

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FirebaseService.initializeFirebase();
runApp(const MyApp());
}

FirebaseService.initializeFirebase 메소드에서 호출한 메소드들 하나씩 설명하겠습니다:

_firebaseMessaging: 저번에 FCM 서버랑 연결하는 포스트 만들 때 어디선가 FirebaseMessaging.instance는 한번 만 불러오는 것이 좋다는걸 읽어서 위에 보시면 처음에 initialize할 때 변수에 할당합니다. 그리고 그 이후 FirebaseService.firebaseMessaging을 쓰고 혹시나 instance가 제대로 안 생겼을까봐 getter으로 null일 때 값을 줍니다.

initializeLocalNotifications: Foreground Notification을 활용하기 위해서는 필요합니다. Android는 InitializeSettings에 꼭 필요한 매개변수가 icon logo 인데, 이것은 android/app/src/main/res/drawable에 있어야합니다.

2번째로 있는 변수가 onSelectNotification인데 이것은 push notification을 사용자가 탭 했을 시 호출됩니다. 만약 이 변수가 없다면 그냥 앱은 켜질 뿐, 다른 액션을 취하지는 않습니다. 여기서 받는 매개변수는 onMessage에 있는 FirebaseMessaging.onMessage.listen 에서 payload입니다. (밑 FCMProvider쪽에서 더 설명하겠습니다).

onMessage: 앱이 foreground에 켜져있을 때 호출됩니다.

onBackgroundMsg: 앱이 꺼져있을 때나 background에 있을 떄 호출됩니다.

Get/Check Device Token

Future<String?> getDeviceToken() async => await FirebaseService.firebaseMessaging.getToken();

DeviceToken을 어떻게 manage할지에 대한 정답은 없지만, Firebase가 추천하는 방법은 여기있습니다. 앱을 처음 킬 때 device token을 서버에 보내고, 그것을 서버에 저장하던, sql로 사용자 device에 저장을 하던, 둘다 하던…다른 방법도 있을꺼고…정답은 없습니다. 저같은 경우는 deviceToken을 처음에 받으면 sql로 사용자 device에 time stamp랑 같이 저장하고, 토큰은 서버로 보냅니다. 그리고 앱을 킬 때마다 저장되어있는 토큰은 다시 서버에 보내는데, time stamp가 한달이 지나면, 새로 갱신해서 보내고 다시 저장하도록 했습니다. 대충 밑에 있는 코드와 다른 메소드들 합쳐서 서버에 보냈습니다 (sql에 대한 메소드들은 다른 곳에서도 쓰이니 관련 파일은 따로 만들어 썼습니다).

Receiving Notification

아쉽게도 제가 서버쪽은 지금 안만들고 있어서, notification test할 때 자세하게는 못했습니다. 그냥 message.notification.title 과 message.notification.body 외에도 message.data로 더 자세하게 테스팅 하고 싶었지만…그건 백앤드 개발자와 같이 협업하면서 테스팅하시면 되겠습니다. Firebase에서 기본적으로 할 수 있는 테스팅만 했습니다. (위에서 device token 프린트해서 firebase console — cloud messaging — compose notification에서 test할 때 device token을 추가하면 됩니다).

3가지 방법으로 푸쉬를 받고/열수 있습니다. (1) 앱이 켜져있고 사용하고 있을 때 (foreground), (2) 앱이 켜져있지만 background에 있을 때 (background), 그리고 (3) 앱이 꺼져있을 때 (terminated).

When App is in the Foreground — Android

일단 기본적인 작동은, 푸쉬를 눌르면 앱이 켜지기만 합니다. 하지만 보통은 푸쉬알림에 해당하는 페이지로 이동하길 원합니다 (이 정보를 message.data에 map형식으로 주면 받았을 때 어느 페이지로 이동해야 되는지 정할 수 있습니다). 저는 푸쉬에 대한 정보를 받을 provider를 만들었고, Navigator.of(context).push를 쓰기 위해서처음앱 켜지는 화면에서 provider의 BuildContext 변수에 값을 할당합니다.

@override
void init() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
FCMProvider.setContext(context);
});
}

앱이 foreground에 있을 때 작동하는 것은 onTapNotification입니다 (처음에 localNotificationsPlugin.initialize할때 들어간 onSelectNotification 콜백입니다). 여기서는 firebase service 에 있는 onMessage메소드에서 message.data.toString()을 payload로 받았다는 가정하에 convertPayload로 다시 map으로 만들어줘서 사용가능하게 합니다 (payload는 string밖에 안됩니다).

When App is in the Background (Android) & When App is in Foreground/Background (iOS)

첫 페이지의 initState에 다음과 같이 해서 안드로이드는 앱이 background에 있을 때 눌렀을때랑, iOS는 foreground & background있을 때 눌렀을 때 작동합니다.

When App is Terminated

앱이 꺼져있는 경우에는 main메소드에서 메시지를 받아와야됩니다. 다른 곳에서 받으려고하면 실패합니다. 그리고 이 메시지를 앱으로 전달해서 첫 페이지에서 매개변수로 받아서 initState에서 받은 정보 갖고 필요한 액션을 취했습니다.

참고로…가끔 앱 종료하고 하는거 테스트 할 때 시간이 꽤 지나고 나서 오는 경우가 있고, 대충 찾아보니 firebase의 socket connection작동 때문인것 같은데…저도 잘 모르겠습니다;;

Backend

iOS 에서 소리가 나게 하려면 cloud console에 밑 코드가 필요합니다. (github)

"apns: { 
"payload": {
"aps": {
"sound": default
}
}
}

--

--