🛩️

Flutter×CloudFunctions超入門 Push通知(FCM)編

2023/09/14に公開

目次

はじめに

CloudFunctionsに関しましてはFlutter×CloudFunctions超入門 セットアップ&基本動作編をご覧ください。

今回はCloudFunctionsとFCM (Firebase Cloud Messaging)を使ってモバイルアプリに必須といっても過言ではないPush通知の実装について紹介します。

Push通知とは

Push通知は当たり前に飛んでくるようで実はその裏では色々なことが起きているようです。
まずAndroidとiOSではPush通知が飛んでくる経路が少し違うようです。
AndroidはAndroid Transport Layer(ATL)、iOSはApple Push Notification Service(APNs)というサービスを経由してそれぞれPush通知が飛んできているみたいです。
これにつきましては以下の記事がとても分かりやすかったです。
https://zenn.dev/wutchy_zenn/scraps/ddbfa54431f49e

こういったことからAndroidはGoogleが親会社ということもあり設定はほとんど必要ありませんが、iOSでは少し設定が必要ということです。
この記事ではFunctionsをメインで紹介しますので、この設定については長くなってしまいそうですので割愛させていただきます。
いつか記事にします!!!!!!!!!
これについては以下の記事がとても分かり易いですので参考すると良いかと思います。
https://zenn.dev/mamushi/articles/flutter_push_notification
全く触れないのも何ですので、ざっくり説明するとFCMでiOSに送るときは開発者のAPNsの証明証が必要だから、AppleからもらってきてFirebaseのプロジェクトに提出するってことが必要になります。

Push通知の仕組み

ここで重要なのはトークンです。
トークンとは、Push通知の送り先の住所みたいなものです。
これをそれぞれユーザーに発行してもらうということになります。
そしてこのトークンを、Functionsに保存していたPush通知を送る関数に持たせて実行すると、相手のもとに飛んでいってくれる仕組みになっています。

FlutterでPush通知実装

今回はとりあえず自分にプッシュ通知を送ってみましょう!

サンプル


1.使用パッケージ

https://pub.dev/packages/firebase_messaging

flutter pub add firebase_messaging

2.Functionsの実装

コード全体

functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

//CloudFunctionsに送る関数
exports.pushTalk = functions.region('asia-northeast1').https.onCall(async (data, _response) => {
  const title = data.title;  //通知のタイトル
  const body = data.body;  //通知の内容
  const token = data.token; //送り先のトークン

 //通知の内容を作る処理
  const message = {
    notification: {
      title: title,
      body: body
    },
    data: {
      title: title,
      body: body
    },
    android: {   //androidの設定
      notification: {
        sound: 'default',
        click_action: 'FLUTTER_NOTIFICATION_CLICK',
      },
    },
    apns: {    //iOSの設定
      payload: {
        aps: {
          badge: 1,
          sound: 'default'
        },
      },
    },
    token: token
  };
  pushToDevice(token, message);   //push通知を送る関数に内容を載せて実行
});

//push通知を送る関数
function pushToDevice(token, payload) {
  admin.messaging().send(payload)
    .then(_pushResponse => {
      return {
        text: token
      };
    })
    .catch(error => {
      throw new functions.https.HttpsError('unknown', error.message, error);
    });
}

細かく解説していきます
前回の復習ですが、今回Functionsに送る関数名はexports.の後に書かれているpushTalkになります。

その後の.region('asia-northeast1')の部分が前回とは少し変わっています。
前回はこれはなかったですが、今回はついでに覚えておきましょう。
これは関数をデプロイするサーバーの地域を指定しています。asia-northeast1は日本サーバーになります。
これを指定することでパフォーマンスが向上してレスポンスが早くなったりします。

次に、今回Flutterから受け取る引数はtitle,body,tokenとなります。
これらの情報を元にmessageという風に変数にまとめています。
このmessageが少々複雑に見えるので細かく見ていきましょう!

  • notification
    • これは表示される通知内容を指定しています。
      分かりやすくいうと、デバイスの通知トレイに表示されるタイトルと本文になります。
flunctions/index.js
 notification: {
      title: title,
      body: body
    },

  • data
    • これは通知を受信したときにアプリ内で使用できるデータです。
    • ここを工夫することで特定の動作をトリガーして通知を開いたときに画面遷移したりすることができるようになります。今回はそういったことはしませんが、いつか紹介します。
flunctions/index.js
 data: {
      title: title,
      body: body
    },

  • android,apns
    • これらはそれぞれandroidとiOSの設定です。冒頭でお話しした通り、androidとiOSは通知が飛んでくる経路がちがいますのでそれぞれ設定を指定してあげる必要があります。

    • sound

      • 通知が来た時の音を指定しています。
      • プロジェクトファイルに.mp3などのサウンドファイルを入れておくことでそれを指定できるみたいです。(僕はやったことありません、、、)
    • click_action

      • 通知をクリックしたときの処理です。
      • URLなどを指定してブラウザを開いたりすることもできるみたいです。
    • badge

      • アプリアイコンの右上に表示されるバッチの数字です。
      • 今回は1を表示するようにしていますが、DBにユーザの未読の通知の数を保存しておくなどをしてバッチの数を指定したりすることが可能になります。
functions/index.js
    android: {
      notification: {
        sound: 'default',
        click_action: 'FLUTTER_NOTIFICATION_CLICK',
      },
    },
    apns: {
      payload: {
        aps: {
          badge: 1,
          sound: 'default'
        },
      },
    },

  • token
    • 送る相手のトークンを指定します。
functions/index.js
 token: token

messageの中に含まれている内容は以上です。


次にfunction pushToDevice(token, payload){...}の部分について解説します

これはindex.js内でプッシュ通知を送る関数を作っているコードです。

Dart風に書くと以下のようになるかと思います。(あくまで風ですので参考にしないでください、、、)
もちろんこのDart風コードは機能しませんがこうなると、jsを書いたことのない方でも何となくjsでどんなことをしているか分かると思います。
これをexports.pushTalkで呼ぶことでPush通知を送ることができます。

index.dart
Future pushToDevice(token, payload) async {
  admin.messaging().send(payload).then((pushResponse) {
    try {
      return {'text': token};
    } catch (e) {
      final e = functions.https.HttpsError('unknown', error.message, error);
      throw e;
    }
  });
}

これであとはデプロイするだけです!!
今回はpushToDeviceだけデプロイしたいので以下のように書くと、指定した関数のみをデプロイできます。

firebase deploy --only functions:pushToDevice

以上でFunctionsの操作は完了です!!
長かったですね、、お疲れさまでした、、
次はFlutterでの実装です!!

3.Flutterの実装

コード全体

main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  final messaging = FirebaseMessaging.instance;

 // 通知の許可をリクエスト
await messaging.requestPermission(
  alert: true,           // 通知が表示されるかどうか
  announcement: false,   // アナウンスメント通知が有効かどうか
  badge: true,           // バッジ(未読件数)が更新されるかどうか
  carPlay: false,        // CarPlayで通知が表示されるかどうか
  criticalAlert: false,  // 重要な通知(サイレントではない)が有効かどうか
  provisional: false,    // 仮の通知(ユーザーによる設定を尊重)が有効かどうか
  sound: true,           // 通知にサウンドが含まれるかどうか
);

// フォアグラウンドで通知が表示されるオプションの設定
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
  alert: true,  // フォアグラウンドで通知が表示されるかどうか
  badge: false, // バッジ(未読件数)が表示されるかどうか
  sound: true,  // 通知にサウンドが含まれるかどうか
);
  runApp(const MyApp());
}
push_page.dart
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

class PushPage extends StatefulWidget {
  const PushPage({super.key});

  
  State<PushPage> createState() => _PushPageState();
}

class _PushPageState extends State<PushPage> {
  String tokenText = 'トークンはまだありません';
  
  final fcm = FirebaseMessaging.instance;
  Future getToken() async {
    final token = await fcm.getToken();
    setState(() {
      tokenText = token!;
    });
  } 
  
  final functions = FirebaseFunctions.instanceFor(region: 'asia-northeast1');
  Future<void> push(String token) async {
    try {
      final HttpsCallable callable = functions.httpsCallable('pushTalk');
      final HttpsCallableResult result = await callable.call({
        'title': 'Push通知テスト',
        'body': '自分から届きました',
        'token': tokenText
      }); // 関数を呼び出し、引数を渡す

      final data = result.data;

      print('結果: ${data}'); // 結果を表示
    } catch (e) {
      print('エラー: $e'); // エラーハンドリング
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Push Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(tokenText),
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: ElevatedButton(
                  onPressed: () async {
                    getToken();
                  },
                  child: const Text('トークン発行')),
            ),
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: ElevatedButton(
                  onPressed: () async {
                    push(tokenText);
                    print(tokenText);
                  },
                  child: const Text('通知')),
            )
          ],
        ),
      ),
    );
  }
}

細かく解説していきます


  • main.dart
    • main()関数内で通知を受け取る準備をします。
    • これは『おまじない』という風に覚えても良いですが、どのように通知を受け取るのかを定義しています。
      記事が長くなってきましたのでコードに直接コメントアウトして書いておりますので参考にしてみてください。。

  • push_page.dart
    ここではまずトークンを取得して、そのトークンを先ほど作ったFunctionsの関数に渡して、自分にPush通知を送るというようなことを実装しています。以下で簡単に解説していきます。

  • トークンを取得

    • FirebaseMessaging.instanceでFirebaseMessagingを初期化します。
    • fcm.getToken();でトークンを取得することができます。
    • 取得したトークンを tokenText = token!;で画面に反映しています。
      このトークンをDBに保存しておくと、送りたい相手のトークンをDBkから取得してきたりできるようになると思います。
push_page.dart
final fcm = FirebaseMessaging.instance;
 Future getToken() async {
   final token = await fcm.getToken();
   setState(() {
     tokenText = token!;
   });
 }
  • Functionsを実行
    • FirebaseFunctions.instanceFor(region: 'asia-northeast1');でFirebaseFunctionsを初期化します。
      • 今回は.instanceFor(region: 'asia-northeast1');となっています。これは先ほどindex.js.region('asia-northeast1')と指定したので、Flutterで初期化するときも同じregionを指定する必要があります。
    • あとは前回Functionsを動かしたようにデータを持たせて先ほどのpushTalkを実行しています。
    • ここで持たせてあげるトークンを変えることで送り先を指定できます。
push_page.dart
 final functions = FirebaseFunctions.instanceFor(region: 'asia-northeast1');
    Future<void> push(String token) async {
    try {
      final HttpsCallable callable = functions.httpsCallable('pushTalk');
      final HttpsCallableResult result = await callable.call({
        'title': 'Push通知テスト',
        'body': '自分から届きました',
        'token': tokenText
      }); // 関数を呼び出し、引数を渡す
    } catch (e) {
      print('エラー: $e'); // エラーハンドリング
    }
  }

これらをそれぞれボタンで実装すると、自分に通知がくると思います!

まとめ

今回はFunctionsとFCMを使ってflutterでPush通知を実装しました。
これを応用することで、いろんなユーザーに通知を送ることができますので、参考にしてみてください!!

次回は『FunctionsでDBを監視』を考えています!

コードは以下のGitにありますので参考にしてみてください!!

https://github.com/sodateya/functions_sample

Flutter大学

Discussion