Flutter ํ…Œํฌ

Flutter + FCM ์—ฐ๋™ (Flutter 3.0์ด์ƒ, ์ƒˆ๋กœ์šดFCM์ ์šฉ๋ฒ„์ „, 3.0์ด์ƒ์ด ์•„๋‹ˆ์—ฌ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค)

๋ฐ๋ถ€์žฅ 2023. 11. 24. 11:33

1. Firebase Cloud Messaging ์ถ”๊ฐ€ํ•˜๊ธฐ

  1. pubspec.yaml ์ˆ˜์ •:

firebase_messaging, flutter_local_notifications ํŒจํ‚ค์ง€๋ฅผ pubspec.yaml ํŒŒ์ผ์— ์ถ”๊ฐ€ ํ›„ pubget

  firebase_messaging: ^14.1.4
  flutter_local_notifications: ^12.0.4

 

2. iOS ์„ค์ •

 [2 - 1] ios/Runner/Info.plist ํŒŒ์ผ์„ ์—ด๊ณ  ๋‹ค์Œ ํ‚ค๋ฅผ ์ถ”๊ฐ€

<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>remote-notification</string>
</array>

 

 [2 - 2] xCode๋ฅผ ์—ด๊ณ Capabilitiesํƒญ์—์„œ PushNotifications, BackgroundMode ํ™œ์„ฑํ™”

 

3. APNs ์ธ์ฆํ‚ค๋ฅผ Firebase๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ

 

 [3 - 1] apple ๊ฐœ๋ฐœ์ž ํŽ˜์ด์ง€์˜ Certificatesํƒญ ์ด๋™ -> +๋ฒ„ํŠผ ํด๋ฆญ

 

 [3 - 2] ํ•˜๋‹จ์˜ Services ์ค‘ Apple PushNotification service SSL(SandBox & Production)ํด๋ฆญ ํ›„ Continue

 

 [3 - 3] ์‚ฌ์šฉํ•  App Id  ์„ ํƒ ํ›„ Continue

 

  [3 - 4] keysํƒญ์œผ๋กœ ์ด๋™ ํ›„ +๋ฒ„ํŠผ ํด๋ฆญ -> ํ‚ค๋„ค์ž„๊ณผ APNs์„ ํƒ ํ›„ Continue -> Registerํด๋ฆญ

 

[3 - 5] Downloadํด๋ฆญ์„ ํ†ตํ•ด APNs๋‹ค์šด๋กœ๋“œ, ํ•œ๋ฒˆ๋งŒ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ๋”ฐ๋กœ ๋ณด๊ด€ํ•„์š”

 

[3 - 6] Firebase์˜ ํ”„๋กœ์ ํŠธ ์„ค์ • ํŽ˜์ด์ง€ ์ด๋™(ํ†ฑ๋‹ˆ๋ฐ”ํ€ด ๋ชจ์–‘) -> ํด๋ผ์šฐ๋“œ๋ฉ”์‹œ์ง• ํƒญ ์ด๋™ -> Apple ์•ฑ ๊ตฌ์„ฑ์˜ APN ์ธ์ฆํ‚ค ์—…๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ

 

[3 - 8] ํ‚คID, ํŒ€ID

 

ํ‚คID : [3 - 5]์—์„œ ๋ณด๋ฉด ์žˆ๋Š” KeyID

ํŒ€ ID : ๊ฐœ๋ฐœ์ž ๊ณ„์ • ํŽ˜์ด์ง€์˜ ๋ฉ”์ธํŽ˜์ด์ง€, Edit ProfileํŽ˜์ด์ง€๋“ฑ์—์„œ ํ™•์ธ๊ฐ€๋Šฅ

 

[3 - 7] ๋‹ค์šด๋กœ๋“œ๋ฐ›์€ .p8ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ  ํ‚คID์™€ ํŒ€ID์ž…๋ ฅ

 

 

 

4. ์•ˆ๋“œ๋กœ์ด๋“œ ์„ค์ •

[4 - 1] android/app/src/main/AndroidManifest.xml์ด๋™ ํ›„ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ applicationํƒœ๊ทธ ๋‚ด๋ถ€์— ๋“ฑ๋กํ•œ๋‹ค

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์•ฑ์˜ ํ‘ธ์‹œ ์ค‘์š”๋„๋ฅผ ๋†’์ด๊ณ  ํ‘ธ์‹œ์•Œ๋ฆผ์˜ ์•„์ด์ฝ˜์„ ์•ฑ์˜ ์•„์ด์ฝ˜์œผ๋กœ ์„ค์ •ํ•œ๋‹ค

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="high_importance_channel" />
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@mipmap/ic_launcher" />
<meta-data
    android:name="firebase_messaging_auto_init_enabled"
    android:value="false" />

 

[์˜ˆ์‹œ]

<application
        android:label="study_project"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:requestLegacyExternalStorage="true" 
        android:usesCleartextTraffic="true">

        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="high_importance_channel" />
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@mipmap/ic_launcher" />
        <meta-data
            android:name="firebase_messaging_auto_init_enabled"
            android:value="false" />
  
  		...
  		...

 

์•„๋ž˜๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ฆญํ•˜์—ฌ ์•ฑ์— ์ง„์ž…ํ•  ๋•Œ ๋ฉ”์‹œ์ง€ ์•ˆ์— ๋‹ด๊ฒจ์žˆ๋Š” ์ •๋ณด๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•จ์ด๋‹ค

์•„๋ž˜ ์ฝ”๋“œ๋Š” activityํƒœ๊ทธ ๋‚ด์— ์ถ”๊ฐ€ํ•œ๋‹ค.

 

<intent-filter>
    <action android:name="FLUTTER_NOTIFICATION_CLICK" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

 

[์˜ˆ์‹œ]

<activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"/>
          ...
		  ...
          <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>    
        </activity>

 

 

3. FCM ์‚ฌ์šฉ์„ ์œ„ํ•œ ์ฝ”๋“œ ์ž‘์„ฑ

[3 -1] main.dartํŒŒ์ผ๋กœ ์ด๋™ -> main()์œ„์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅ

Flutter 3.0์ด์ƒ ๋ฐ FCM ์ƒˆ๋กœ์šด ๋ฒ„์ „์—์„œ ๋ฌธ์ œ์—†์ด ์ˆ˜์‹ ํ•˜๋ ค๋ฉด

@pragma('vm:entry-point')๋Š” ํ•„์ˆ˜

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

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  if (Firebase.apps.isEmpty) {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );
  }
}

late AndroidNotificationChannel channel;
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

 

[3 -2] ํ‘ธ์‹œ์•Œ๋ฆผ์ˆ˜์‹  ๊ถŒํ•œ ์š”์ฒญ์„ ์œ„ํ•ด permission_handler ํŒจํ‚ค์ง€ ์„ค์น˜

permission_handler: ^10.4.3

 

[3 - 3] main()์•ˆ์— ์•„๋ž˜์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€

// FCMํ˜ธํ™˜์—ฌ๋ถ€ ํ™•์ธ
bool _isFCMSupport = await FirebaseMessaging.instance.isSupported();

if (_isFCMSupport) {
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  channel = const AndroidNotificationChannel(
    'high_importance_channel', // id
    'High Importance Notifications', // title
  );

  var initialzationSettingsAndroid =
  AndroidInitializationSettings('@mipmap/ic_launcher');

  var initialzationSettingsIOS = DarwinInitializationSettings(
    requestSoundPermission: true,
    requestBadgePermission: true,
    requestAlertPermission: true,
  );

  flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
      AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  var initializationSettings = InitializationSettings(
    android: initialzationSettingsAndroid,
    iOS: initialzationSettingsIOS,
  );

  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
  );

  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );
  await FirebaseMessaging.instance.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );
}

// web์—์„œ ๊ถŒํ•œ์š”์ฒญ์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฒดํฌ
if (!kIsWeb) {
  // ํ‘ธ์‹œ์•Œ๋ฆผ ๊ถŒํ•œ์š”์ฒญ
  Map<Permission, PermissionStatus> statuses = await [
    Permission.notification,
  ].request();
}

 

์•ฑ์˜ ์‹œ์ž‘์ ์— ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€(๊ธฐ๋ณธ Flutter ์‹œ์ž‘ ํ”„๋กœ์ ํŠธ ๊ธฐ์ค€ MyHomePage Class๋‚ด๋ถ€

import 'package:flutter/foundation.dart' as foundation;

@override
  void initState() {
    super.initState();
    forgroudInit();
    backgroundInit();
    terminateInit();
  }

//foground
  forgroudInit() {
    FirebaseMessaging.onMessage.listen((RemoteMessage? message) {
      if (message != null) {
        if (message.notification != null) {
          print('title ${message.notification!.title}');
          print('body ${message.notification!.body}');
          print('click_action ${message.data["click_action"]}');
           // ์•ˆ๋“œ๋กœ์ด๋“œ๋Š” ํŒ์—…์ฐฝ์ด ์•ˆ๋œจ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€
          try {
            if (foundation.defaultTargetPlatform != foundation.TargetPlatform.iOS) {
              showMessage(message);
            }
          } catch(e) {
            debugPrint('onMessage try error :${e}');
          }
        }
      }
    });
  }

  backgroundInit() {
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage? message) {
      if (message != null) {
        if (message.notification != null) {
          print('title ${message.notification!.title}');
          print('body ${message.notification!.body}');
          print('click_action ${message.data["click_action"]}');
        }
      }
    });
  }

// ์•ฑ ์ข…๋ฃŒ์ƒํƒœ์—์„œ
  terminateInit() {
    FirebaseMessaging.instance
        .getInitialMessage()
        .then((RemoteMessage? message) {
      if (message != null) {
        if (message.notification != null) {
          print('title ${message.notification!.title}');
          print('body ${message.notification!.body}');
          print('click_action ${message.data["click_action"]}');
        }
      }
    });
  }
  
  showMessage(RemoteMessage? message) {

    RemoteNotification? notification = message?.notification;
    AndroidNotification? android = message?.notification?.android;
    var data = message?.data;
    var androidNotiDetails = AndroidNotificationDetails(
      channel.id,
      channel.name,
      importance: Importance.high,
    );

    var iOSNotiDetails = const DarwinNotificationDetails();
    var details =
    NotificationDetails(android: androidNotiDetails, iOS: iOSNotiDetails);
    if (data != null) {
      flutterLocalNotificationsPlugin.show(
        notification.hashCode,
        message?.notification?.title,
        message?.notification?.body,
        details,
        payload: 'data',
      );
    }
  }

 

4. Firebase FCM ํ…Œ์ŠคํŠธ

 

[4 - 1] Firebase์˜ ์ฐธ์—ฌํƒญ์— ์žˆ๋Š” Messagingํด๋ฆญ

 

[4 - 2] Firebase ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ํด๋ฆญ

 

[4 - 3]  ์ œ๋ชฉ, ๋‚ด์šฉ ์ž…๋ ฅ

 

[4 - 5] ํƒ€๊ฒŸ ํ”Œ๋žซํผ ์„ ํƒ

 

[4 - 6] ๋‹ค์Œ ์ญ‰ ํด๋ฆญ ํ›„ ๊ฒ€ํ†  ํด๋ฆญ

 

[4 - 7] ์ˆ˜์‹ ๋˜๋Š” ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค