iOS FCM 설정방법은 여기에 잘 나와있음
💡 키 ID는 key만드는 곳에서 쉽게 확인 가능
💡 팀 ID는 애플디벨로퍼 → 멤버십 세부사항 → 팀ID에서 확인가능

xCode에서 Push관련된 설정을 하고, 애플디벨로퍼에서 push Capabilities가 포함된 identifier을 새로 등록 해줘야한다.

가장 쉬운 방법은 FCM 콘솔에서 메시징을 보내는 것이다.
하지만 data 영역을 넣어서 테스트 해 볼 수 없다는 단점이 있음
테스트 사이트 이용
사이트를 이용하면 data 영역도 넣어서 테스트 해 볼 수 있다.
나는 토큰을 이용해서 테스트했다.

엑세스토큰은 여기를 참고해서 얻는다.
프로젝트ID는 firebase console -> 프로젝트 개요 옆 설정 아이콘 클릭 -> 일반 탭 -> 프로젝트ID에서 찾을 수 있다.
디바이스토큰은 흔히 말하는 fcm토큰을 의미한다.
//이 코드를 통해 얻을 수 있음
//딜레이 1초를 하지 않으면 iOS에서 에러가난다.
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
Future.delayed(Duration(seconds: 1), () async {
String? fcmToken = await FirebaseMessaging.instance.getToken();
debugPrint("FCM Token: $fcmToken");
});
플러터 FCM관련 코드는 봐도봐도 적응이 안된다 너무 복잡함 🤬
os자체도 2개인데다가, 앱 상태(포그라운드, 백그라운드, 터미네이트) 상태, 알림이 수신 되었을때 & 알림을 클릭 했을 때 상황을 모두 고려해야하면 상황이 12가지나 됨.. 🤬
더군다나 안드로이드는 포그라운드일 때 헤드업 알림을 띄우지 않는게 os 설정이라, local_notification 이라는 디펜던시를 이용해서 추가로 포그라운드일 때 헤드업 알림이 보이도록 구현해줘야한다.
말로만 해도 너무 복잡.. 😵💫
그래도 이번에 최대한 깔끔하게 정리했고, 삽질한 내용도 최대한 주석으로 정리했다.
//main.dart
//어떤 클래스에 속한 함수가 아니라 최상단 함수로 구현해야함
('vm:entry-point')
Future < void > handleFcmBackgroundMessage(RemoteMessage message) async {
debugPrint('$debuggingLog handleFcmBackgroundMessage: $message');
//iOS는 알림이 올때 뱃지를 자동으로 구현해 주지 않기 때문에, 여기서
//flutter_app_badge_control 이 디펜던시를 활용해 직접 구현해줘야함
// 그건 다음번에.. 🥹
}
Future < void > main() async {
WidgetsFlutterBinding.ensureInitialized();
//initialize firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Future.delayed(Duration(seconds: 1), () async {
// String? fcmToken = await FirebaseMessaging.instance.getToken();
// debugPrint("FCM Token: $fcmToken");
// });
//initialize fcm
FcmService().initializeFcmService();
// [안드로이드 - 백그라운드, 터미네이트 / iOS - 백그라운드, 터미네이트] 알림을 수신할 때(클릭X) 호출됨
// main()함수 안에 있어야 제대로 작동함
/* 서버에서 아래와 같은 형식으로 알림을 보내야 작동함
* apns: {
payload: {
aps: {
'mutable-content': 1,
'content-available': 1
}
}
}*/
FirebaseMessaging.onBackgroundMessage(handleFcmBackgroundMessage);
runApp(const MyApp());
}
//fcm_util.dart
/* [Android]
* 포그라운드 알림 수신 -> displayLocalNotification
* 포그라운드 알림 클릭 -> _handleLocalNotificationTap
*
* 백그라운드 알림 수신 -> handleFcmBackgroundMessage
* 백그라운드 알림 클릭 -> _handleFcmMessageOpenedApp
*
* 터미네이트 알림 수신 -> handleFcmBackgroundMessage
* 터미네이트 알림 클릭 -> _handleFcmInitialMessage
*
* */
/* [iOS]
* 포그라운드 알림 수신 -> displayLocalNotification
* 포그라운드 알림 클릭 -> _handleFcmMessageOpenedApp
*
* 백그라운드 알림 수신 -> handleFcmBackgroundMessage
* 백그라운드 알림 클릭 -> _handleFcmMessageOpenedApp
*
* 터미네이트 알림 수신 -> handleFcmBackgroundMessage
* 터미네이트 알림 클릭 -> _handleFcmInitialMessage
*
* */
bool isLocalNotificationInitialized = false;
late AndroidNotificationChannel notificationChannel;
late FlutterLocalNotificationsPlugin localNotificationPlugin;
late FirebaseMessaging fcmInstance;
class FcmService {
Future < void > initializeFcmService() async {
fcmInstance = FirebaseMessaging.instance;
//////////////로컬 알림 초기화 및 설정//////////////
localNotificationPlugin = FlutterLocalNotificationsPlugin();
if (isLocalNotificationInitialized) {
return;
}
// 알림 채널 설정
notificationChannel =
const AndroidNotificationChannel(
'channelID',
'channelName',
description: 'This is channel description.',
importance: Importance.high,
);
// 알림 채널 생성
await localNotificationPlugin
.resolvePlatformSpecificImplementation <
AndroidFlutterLocalNotificationsPlugin > ()
?.createNotificationChannel(notificationChannel);
isLocalNotificationInitialized = true;
// 알림 설정
await localNotificationPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(),
),
onDidReceiveNotificationResponse: _handleLocalNotificationTap,
);
//////////////////////////
// FCM 설정
await _configureFcmSettings();
// FCM 메시지 핸들러 설정
_setupFcmMessageHandlers();
}
/// FCM 기본 설정
Future < void > _configureFcmSettings() async {
// FCM 포그라운드 알림 설정
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
// FCM 권한 요청
await fcmInstance.requestPermission();
fcmInstance.setAutoInitEnabled(true);
}
// [안드로이드 - 포그라운드] (로컬)알림을 클릭했을 때 호출됨
void _handleLocalNotificationTap(NotificationResponse response) {
debugPrint('$debuggingLog _handleLocalNotificationTap: $response');
}
/// FCM 메시지 핸들러 설정
void _setupFcmMessageHandlers() {
// [안드로이드 - 포그라운드 / iOS - 포그라운드] 알림을 수신할 때(클릭X) 호출됨
FirebaseMessaging.onMessage.listen(displayLocalNotification);
// [안드로이드 - 백그라운드 / iOS - 포그라운드, 백그라운드] 알림을 클릭했을 때 호출됨
FirebaseMessaging.onMessageOpenedApp.listen(_handleFcmMessageOpenedApp);
//[안드로이드 - 터미네이트 / iOS - 터미네이트] 알림을 클릭했을 때 호출됨
// 안드로이드,iOS - 알림을 클릭해서 앱이 시작된것이 아니라 터미네이트 상태에서 런쳐아이콘을 클릭해 앱을 시작한 경우에도 호출됨 -> RemoteMessage null로 들어옴
fcmInstance.getInitialMessage().then(_handleFcmInitialMessage);
}
void _handleFcmMessageOpenedApp(RemoteMessage message) async {
removeIosBadge();
debugPrint('$debuggingLog _handleFcmMessageOpenedApp: $message');
}
void _handleFcmInitialMessage(RemoteMessage ? message) async {
removeIosBadge();
debugPrint('$debuggingLog _handleFcmInitialMessage: $message');
}
removeIosBadge() {
FlutterAppBadgeControl.removeBadge();
}
}
/// 로컬 알림 표시 함수
// 안드로이드만 포그라운드 시 localNotification으로 알림을 표시하면됨
// iOS는 localNotification 사용 시 알림이 두번 뜸
Future < void > displayLocalNotification(RemoteMessage message) async {
debugPrint('$debuggingLog displayLocalNotification: $message');
RemoteNotification ? notification = message.notification;
AndroidNotification ? android = message.notification?.android;
if (notification != null && android != null) {
http.Response ? response;
ByteArrayAndroidBitmap ? notificationImage;
StyleInformation ? imageStyle;
// 이미지가 있는 경우 처리
if (android.imageUrl != null) {
response = await http
.get(Uri.parse(message.notification!.android!.imageUrl ?? ''));
notificationImage = ByteArrayAndroidBitmap.fromBase64String(
base64Encode(response.bodyBytes));
imageStyle = BigPictureStyleInformation(
notificationImage,
largeIcon: notificationImage,
hideExpandedLargeIcon: true,
);
}
// 로컬 알림 표시
localNotificationPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
notificationChannel.id,
notificationChannel.name,
channelDescription: notificationChannel.description,
icon: '@mipmap/ic_launcher',
largeIcon: notificationImage,
priority: Priority.high, // 이부분을 high이상으로 설정해야 head-up 알림으로 작동함
importance: Importance.high, // 이부분을 high이상으로 설정해야 head-up 알림으로 작동함
styleInformation: imageStyle,
)
),
payload: message.data.toString(),
);
}
}