Flutter + FCM ์ฐ๋ (Flutter 3.0์ด์, ์๋ก์ดFCM์ ์ฉ๋ฒ์ , 3.0์ด์์ด ์๋์ฌ๋ ๊ฐ๋ฅํฉ๋๋ค)
1. Firebase Cloud Messaging ์ถ๊ฐํ๊ธฐ
- 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] ์์ ๋๋ ๋ด์ฉ์ ํ์ธํ ์ ์๋ค