背景
FlutterでFirestoreのPush通知を実装する機会があり、大変複雑だったため備忘録として記録します。
目標
iOS、Androidで以下の状態で動作することを目標に設定しました。
・フォアグラウンド、バックグラウンド、アプリをキルした状態で通知が受け取れる
・通知受け取り時に通知バナーが表示される
事前設定
事前に以下の設定を行っておきます。(iOS向け)
・Apple Developerのidentifier>CapabilityにPush Notificationを追加
・Apple DeveloperからKeyファイルを作成、ダウンロード(.p8ファイル)
・Firebaseのプロジェクトの設定>Cloud Messaging>Apple アプリの構成 > APNs 認証キー へKeyファイルをアップロード
パッケージのインストール
pubspec.yamlに使用するパッケージを入力し、保存します。
dependencies:
firebase_messaging: ^15.0.1
flutter_local_notifications: ^17.1.2
※flutter_local_notificationsはAndroidのフォアグラウンド通知で使用します。
ターミナルで下記のコマンドを実行します。
flutter pub get
iOSの設定
XCode
Runner > Target Runner > Signing & Capabilities で+Capabilityボタンから、以下を追加します。
・Push Notifications
・Background Modes
Background Modesは、以下にチェックを入れてください。
・Background fetch
・Remote notifications
Androidの設定
・Manifest.xmlの設定は以下の通りです。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 追加 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- 追加 -->
iOS、Androidバックグラウンド 実装
main.dart
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Firebase初期化処理
await Firebase.initializeApp();
// バックグラウンド通知の設定
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(
const ProviderScope(
child: App(),
),
);
}
Push通知を管理するクラス
※AppPushMessageは独自で定義したPush通知のメッセージを受け取るモデルです。FCMとLocalNotificationで受け取る型が違うので、AppPushMessageに変換して使用しています。
class PushNotificationRepository {
/// FCM Tokenの取得
Future<String?> getFcmToken() async {
final fcmToken = await FirebaseMessaging.instance.getToken();
return fcmToken;
}
/// Push通知の許可設定
Future<void> requestPermission() async {
// フォアグラウンドで通知が表示されるオプションの設定
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,
);
}
/// Push通知初期設定
Future<void> initializePushNotification({
// Push通知タップ後の処理
required void Function(AppPushMessage) onTapMessage,
}) async {
// 通知許可のダイアログを表示
requestPermission();
}
/// バックグラウンド
Future<void> onMessageOpenedApp({
// Push通知タップ後の処理
required void Function(AppPushMessage) onTapMessage,
}) async {
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final appPushMessage = _convertRemoteMessage(message);
onTapMessage(appPushMessage);
});
}
/// アプリが起動していない状態
Future<void> onTerminatedMessage({
// Push通知タップ後の処理
required void Function(AppPushMessage) onTapMessage,
}) async {
final remoteMessage = await FirebaseMessaging.instance.getInitialMessage();
if (remoteMessage != null) {
final appMessage = _convertRemoteMessage(remoteMessage);
onTapMessage(appMessage);
}
}
}
AppPushMessage _convertRemoteMessage(RemoteMessage message) {
return AppPushMessage(
hash: message.hashCode,
title: message.notification?.title ?? '',
message: message.notification?.body ?? '',
);
}
AppPushMessage _convertNotificationResponse(
NotificationResponse response) {
return AppPushMessage(
hash: response.id ?? 0,
);
}
Pusu通知の初期化設定
Future<void> initializePushNotification(
{Function(AppPushMessage)? onTapMessage}) async {
// FCMトークン取得
await pushNotificationRepository.getFcmToken();
// iOS,Androidバックグラウンド設定
pushNotificationRepository.onMessage(onTapMessage: dispatchOnMessage);
// アプリがキルされている状態設定
pushNotificationRepository.onTerminatedMessage(
onTapMessage: onTapMessage,
);
}
}
Androidのフォアグラウンド通知
Androidでフォアグラウンド通知を受信するには、別途設定が必要です。
ローカル通知を管理するクラスを追加
class LocalNotificationManager {
AndroidNotificationChannel androidNotificationChannel =
const AndroidNotificationChannel(
'channel_id',
'channel_name',
importance: Importance.max,
);
/// ローカル通知の初期化設定
Future<void> initialLocalNotification({
required AppPushMessage Function(NotificationResponse)
convertToAppPushMessage,
required void Function(AppPushMessage) onTapMessage,
}) async {
const initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_notification');
const initializationSettingsIos = DarwinInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIos,
);
await FlutterLocalNotificationsPlugin().initialize(
initializationSettings,
onDidReceiveBackgroundNotificationResponse: (message) {
final appPushMessage = convertToAppPushMessage(message);
onTapMessage(appPushMessage);
},
);
await FlutterLocalNotificationsPlugin()
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(androidNotificationChannel);
}
/// 通知を表示
Future<void> showPushNotification(AppPushMessage appPushMessage) async {
await FlutterLocalNotificationsPlugin().show(
appPushMessage.hash, // プッシュ通知のハッシュ値
appPushMessage.title, // プッシュ通知のタイトル
appPushMessage.message, // プッシュ通知のメッセージ
NotificationDetails(
android: AndroidNotificationDetails(
androidNotificationChannel.id,
androidNotificationChannel.name,
icon: '@mipmap/ic_notification',
),
),
);
}
}
プッシュ通知を管理するクラスにLocalNotificationManagerの初期化処理と、フォアグラウンド通知を受け取る処理を追加
class PushNotificationRepository {
...
final FlutterLocalNotificationsPlugin localNotificationManager;
/// Push通知初期設定
Future<void> initializePushNotification({
// Push通知タップ後の処理
required void Function(AppPushMessage) onTapMessage,
}) async {
// 通知許可のダイアログを表示
requestPermission();
// ローカル通知の初期化 ←追加
localNotificationManager.initialLocalNotification(
// convertToAppPushMessageにLocalNotificationで受け取ったRemoteMessageをAppPushMessage(独自のモデル)に変換する処理
convertToAppPushMessage: _convertNotificationResponse,
onTapMessage: onTapMessage,
);
}
...
/// Androidのフォアグラウンド通知
Future<void> onMessage({
// Push通知タップ後の処理
required void Function(AppPushMessage) onTapMessage,
}) async {
FirebaseMessaging.onMessage.listen((message) async {
final appPushMessage = _convertRemoteMessage(message);
AndroidNotification? android = message.notification?.android;
if (android != null) {
await localNotificationManager.showPushNotification(appPushMessage);
}
});
}
...
}
プッシュ通知の初期化処理にAndroidフォアグラウンド設定を追加する。
Future<void> initializePushNotification(
{Function(AppPushMessage)? onTapMessage}) async {
// FCMトークン取得
await pushNotificationRepository.getFcmToken();
// Androidフォアグラウンド設定 ←追加
pushNotificationRepository.initializePushNotification(
onTapMessage: onTapMessage!,
);
// iOS,Androidバックグラウンド設定
pushNotificationRepository.onMessage(onTapMessage: dispatchOnMessage);
// Androidフォアグラウンド設定 ←追加
pushNotificationRepository.onMessageOpenedApp(onTapMessage: (message) {
onTapMessage(message);
});
// アプリがキルされている状態設定
pushNotificationRepository.onTerminatedMessage(
onTapMessage: onTapMessage,
);
}
}
呼び出し
作成したinitializePushNotificationの初期化処理をApp.dartなどで呼び出すと、Push通知のタップ後の処理も設定することができます。
(クラスは省略)
initializePushNotification(onTapMessage: (message) {
// Push通知タップ後に実行したい処理を渡す
print('プッシュ通知が呼ばれたよ');
});
Android:バックグラウンド状態で通知バナーを表示する様にする
そのままの設定ではアプリキルの状態で通知を受け取った際にAndroidでは通知バナーが表示されないので設定を追加します。
AndroidのManifest.jsonの設定
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" /> ←通知アイコン
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id"/> ←デフォルトの通知IDを定義
build.gradle
Manifest.jsonで定義したdefault_notification_channel_idにlocalNotificationで設定したチャネルIDを設定します。
以下の設定で、バックグラウンド状態でも通知バナーが表示されるようになります。
defaultConfig {
...
// localNotificationで設定したチャネルIDを設定する
resValue "string", "default_notification_channel_id", "channel_id"
...
}
参考記事
Flutter で Firebase Cloud Messaging クライアント アプリを設定する
Firebase Notifications & Flutter Local Notifications
告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。
みなさまからのご応募をお待ちしております。