【Flutter】FCMとSupabase Edge FunctionsでPush通知〜第2回〜

【Flutter】FCMとSupabase Edge FunctionsでPush通知〜第2回〜

前回に続き、今回は、Push通知の受信までの実装を進めていきます。

改めて開発の流れの紹介

  1. Firebaseの設定
  2. Firebaseの環境分け(devとprod)
  3. FlutterでFirebase初期化を実装
  4. FCMの設定【← 第1回はここまで】
  5. Flutterで通知許可ダイアログを実装
  6. バックグラウンド・フォアグラウンド・ターミネート状態でのPush通知の受信
  7. Firebase Messagingでテスト送信【← 第2回はここまで】
  8. Supabaseインストール
  9. Supabase Edge Functionsの実装
  10. Webhookを設定
  11. SupabaseからFlutterアプリにPush通知を送信【← 第3回はここまで】

Flutterで通知許可ダイアログを実装

許可ダイアログを表示したい箇所に以下コードを記載します。

await ref.read(pushNotificationRepositoryProvider).requestPermission();

バックグラウンド・フォアグラウンド・ターミネートでのPush通知を実装

バックグラウンド・フォアグラウンド・ターミネート状態でのPush通知を受信する実装です。

フォアグラウンド・ターミネート通知に関しては、デフォルトのままだと通知を受信しても、通知バーに表示されないようになっています。

通知を表示するために、flutter_local_notificationsパッケージを使ってカスタム通知を表示する方法が一般的です。

android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

ios/Runner/AppDelegate.swift

import UIKit
import Flutter
import flutter_local_notifications

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // 以下追加
    FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
      GeneratedPluginRegistrant.register(with: registry)
    }

    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
    }
    // ここまで

main.dart

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  logger.i('Handling a background message: ${message.messageId}');
}

class _AppInitializer extends HookConsumerWidget {
  const _AppInitializer({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ...
    
    Future<void> init() async {
      ref.read(pushNotificationRepositoryProvider).initialize(
            handler: _firebaseMessagingBackgroundHandler,
          );
      ref.read(pushNotificationRepositoryProvider).handle();
      ...
    }
final pushNotificationRepositoryProvider =
    Provider<PushNotificationRepository>((ref) {
  return PushNotificationRepository(ref);
});

class PushNotificationRepository {
  PushNotificationRepository(this._ref);
  final Ref _ref;

  final messaging = FirebaseMessaging.instance;
  final onMessaging = FirebaseMessaging.onMessage;
  final spabase = Supabase.instance.client;
  final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  final channel = const AndroidNotificationChannel(
    'fcm_channel',
    'Fcm Notifications',
    description: 'This channel is used for Fcm notifications.',
    importance: Importance.high,
  );

  void initialize({
    required Future<void> Function(RemoteMessage) handler,
  }) {
    FirebaseMessaging.onBackgroundMessage(handler);

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

    messaging.setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );
  }

  void handle({VoidCallback? callbackRouter}) {
    // プッシュ通知を開いた時のハンドリング
    void handleMessage(RemoteMessage message) {
      final callback = callbackRouter;

      if (callback != null) {
        callback();
      }
    }

    // Terminatedから開いた時
    messaging.getInitialMessage();

    // Backgroundから開いた時
    FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);

    FirebaseMessaging.onMessage.listen((message) {
      final notification = message.notification;
      final android = message.notification?.android;

      if (notification != null && android != null) {
        flutterLocalNotificationsPlugin.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              channel.id,
              channel.name,
              channelDescription: channel.description,
              icon: '@mipmap/ic_stat',
            ),
          ),
        );
      }
    });
  }

  Future<String?> getToken() async {
    return messaging.getToken();
  }

  Future<void> requestPermission() async {
    final settings = await messaging.requestPermission();

    if (settings.authorizationStatus == AuthorizationStatus.authorized ||
        settings.authorizationStatus == AuthorizationStatus.provisional) {
      await setupFcm();
    }
  }

  Future<void> setupFcm() async {
    final fcmToken = await getToken();
    if (fcmToken != null) {
      logger.i('🐯 FCM TOKEN: $fcmToken');
      await saveFcmToken(fcmToken);
    }

    messaging.onTokenRefresh.listen((newToken) async {
      await saveFcmToken(newToken);
    });
  }

  Future<void> saveFcmToken(String fcmToken) async {
    // NOTE: FCM TokenをDBにセット(ご自身の環境に合わせて記載してください)
  }

  // 全体配信・Topicでsubscribe
  void subscribeToTopic() {
    messaging.subscribeToTopic('all');
  }
}

これでPush通知を受信できるようになります。

Firebase Messagingでテスト送信して受信を確認

実際に、FirebaseからPush通知を送信してみます。

通知の作成から手動でテスト送信しましょう。

Firebaseメッセージングのオンボーディング
Firebaseメッセージングの通知の作成
Firebaseメッセージングでテスト送信

バックグラウンドでのPush通知の受信を確認

Androidはシミュレーター上で確認できます。

AndroidでバックグラウンドでのPush通知の受信を確認

iOSは実機ビルドが必要です。

iOSでバックグラウンドでのPush通知の受信を確認

フォアグラウンド・ターミネートでのPush通知の受信を確認

フォアグラウンド・ターミネートでのPush通知の受信を確認
フォアグラウンド・ターミネートでのPush通知の受信を確認
フォアグラウンド・ターミネートでのPush通知の受信を確認

今回はこれで一旦完了です。

続きは以下の記事を見てください!

Anycloudではプロダクト開発の支援を行っています

プロダクト開発をお考えの方はぜひAnycloudにご相談ください。

まずは相談する

記事を書いた人

Matsuura

エンジニア

Matsuura

Twitter

Anycloudでエンジニアしてます!主にFlutterやWebフロントをやっていて、最近はバックエンドやインフラに挑戦・苦戦中。