본문 바로가기
프로그래밍/Flutter

[Flutter] FCM 기능 추가

by YuminK 2023. 10. 23.

Flutter 환경에서의 Firebases는 상대적으로 쉽게 설정이 가능하다.

FlutterFire CLI에서 기본적인 설정을 다 해주기 때문이다.

따로 패키지명 가지고 Firebase Console에 등록할 필요도 없다. (프로젝트는 추가해야 한다)

 

1. FlutterFire 설정

npm을 이용하여 설치하고 firebase 로그인, cli 활성화 과정을 거친다.

dart pub global activate flutterfire_cli

flutterfire configure - 안 되면 에러메시지 보고 환경변수 추가

 

여기까지 하면 lib 파일쪽에 firebase_options 파일이 추가되고,

android, ios 쪽에 google-service 파일도 자동으로 추가 된다. 

참고: https://firebase.flutter.dev/docs/overview

 

*설명보다 코드 먼저 보고 싶으면 하단으로 이동

 

2. Foreground 

기본적으로 foregrond에 있는 상태에서는 알림이 오지 않는데,

안드로이드에서 채널 우선순위를 높이고 메타데이터를 등록한다.

 

iOS는 presentationOption으로 설정하면 된다.

참고: https://firebase.flutter.dev/docs/messaging/notifications#foreground-notifications

 

종료된 상태에서 메시지를 클릭한 경우 getInitialMessage 함수를 호출하면 데이터가 존재한다고 한다. 

백그랑운드 상태에서 받은 메시지를 클릭하는 경우 onMessageOpenedApp 콜백으로 들어온다.

 

메시지를 받아서 화면 이동을 시키고 싶은 경우에는 이 문서를 보면 좋을 것 같다.

https://firebase.flutter.dev/docs/messaging/notifications/#handling-interaction

 

Firebase Android SDK는 일단 FCM 알림을 막도록 되어 있다. 그러나 이벤트로 들어오는 메시지는 처리할 수 있는데 이때 flutter_location_notifications 패키지를 사용하여 알림을 처리할 수 있다.

 

3. Background

안드로이드는 백그라운드에서 앱이 종료된 상태에서도

메시지를 받도록 처리하는 ioslation의 개념이 있다고 한다. 

 

백그라운드 메시지를 처리하는 핸들러 다음과 같은 제약 조건이 있다.

It must not be an anonymous function.

It must be a top-level function (e.g. not a class method which requires initialization).

 

4. Apple Integration

iOS부분에서 3번, 4번 (APN 등록, xcode에서 알림 설정 등) 작업을 해줘야 한다.

참고: https://kymworld.tistory.com/168

 

기타

iOS 시뮬레이터 환경에서는 FCM 기능이 동작하지 않는다고 한다. 실기기로 테스트하자. 

 

Firebase Console에서 클라우드 메시지를 보냈을 때 정상적으로 오는지 확인한다.

android: foreground, background, terminated

iOS   : foreground, background, terminated 

 

구현 소스(Foregrond, Backgrond, Token 받아오는 코드)

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // 권한 요청 처리
    FirebaseMessaging messaging = FirebaseMessaging.instance;
    NotificationSettings settings = await messaging.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

    print('User granted permission: ${settings.authorizationStatus}');

    // Foreground: iOS 설정
    await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
      alert: true, // Required to display a heads up notification
      badge: true,
      sound: true,
    );

    // Foreground: Android 설정(우선순의를 올려서 앱 실행중에 알림이 나오도록 처리)
    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      'high_importance_channel', // id
      'High Importance Notifications', // title
      importance: Importance.max,
    );

    ///////////////////////////////////////////

    // Create the channel on the device (if a channel with an id already exists, it will be updated):
    final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

    AndroidInitializationSettings initSettingsAndroid =
    const AndroidInitializationSettings('@mipmap/ic_launcher');
    DarwinInitializationSettings initSettingsIOS =
    const DarwinInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );

    final InitializationSettings initializationSettings = InitializationSettings(
      android: initSettingsAndroid,
      iOS: initSettingsIOS,
    );
    flutterLocalNotificationsPlugin.initialize(initializationSettings);
    ///////////////////////////////////////////

    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);
    /*
    Firebase Android SDK will block displaying any FCM notification no matter what Notification Channel has been set.
    We can however still handle an incoming notification message via the onMessage stream and
    create a custom local notification using flutter_local_notifications:

    Foreground 메시지 콜백 부분
   */
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      AndroidNotification? android = message.notification?.android;

      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}');
      }

      // If `onMessage` is triggered with a notification, construct our own
      // local notification to show to users using the created channel.
      if (notification != null && android != null) {
        flutterLocalNotificationsPlugin.show(
            notification.hashCode,
            notification.title,
            notification.body,
            NotificationDetails(
              android: AndroidNotificationDetails(
                channel.id,
                channel.name,
                icon: android?.smallIcon,
                // other properties...
              ),
            ));
      }
    });

    // Background: Android 설정(isolation)
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(child: const MyApp());
}

/*
It must not be an anonymous function.
It must be a top-level function (e.g. not a class method which requires initialization).
 */
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  // await Firebase.initializeApp();

  print("Handling a background message: ${message.messageId}");
}

 Future<void> setupToken() async {
    FirebaseMessaging.instance.onTokenRefresh.listen(saveTokenToDatabase);
    String? token = await FirebaseMessaging.instance.getToken();
    print("token = $token");

    if(token != null) {
      await saveTokenToDatabase(token);
    }
  }

  Future<void> saveTokenToDatabase(String token) async {
    
  }

 

참고자료

https://firebase.flutter.dev/docs/messaging/usage

https://firebase.flutter.dev/docs/messaging/server-integration/

https://firebase.flutter.dev/docs/messaging/apple-integration/

https://firebase.flutter.dev/docs/messaging/notifications/#advanced-usage

댓글