🔔

Push通知をやってみた

2023/04/22に公開

通知を送るだけでもやってみたい

FirebaseのFCMを使うと、スマートフォンに通知を送ることができます。しかし、Cloud Functionsでのやり方が紹介されている方法がないような気がする。
今回は本当に通知が来ているのか確認するのを実験してみた。
FCMについて解説されているサイト
https://firebase.google.com/docs/cloud-messaging?hl=ja

2023年 5/24水曜日に、iOSでも動作検証をやってみました!
Daigoさんの記事を参考に進めるとできました
https://zenn.dev/mamushi/articles/flutter_push_notification
IDの登録ができなくて、詰まってしまうところがありましたが、出来ていた?
1週間経ってから試すか、Appleにお問合せをする必要があるそうですが、僕はしなくてもうまくいきました?
https://note.com/saburo_engineer/n/nda2b7dec58b6

環境構築をする

Firebaseのプロジェクトを作っておいてください。GoogleAnlyticsは、FCMと連携しているようなので、有効にして、プロジェクトを作成してください。
プロジェクトができたら、Firestoreを使える状態にして、Cloud Functionsを有効化しましょう。

Cloud Functionsを使用するので、有料プランに変更しておいてください。
こちらを参考に設定をします。
https://firebase.google.com/docs/functions/get-started?hl=ja

今回は、JavaScriptを使用します。ESLintはOFFにしておきましょう。
書くコードは、これだけです。notificationsコレクションに、データとトークンが保存されたら、通知を端末に送ります。

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

admin.initializeApp();

exports.sendNotificationOnNewDocument = functions.firestore
  .document('notifications/{notificationId}')
  .onCreate(async (snapshot, context) => {
    const notificationData = snapshot.data();

    const message = {
      notification: {
        title: notificationData.title,
        body: notificationData.body,
      },
      token: notificationData.token,
    };

    try {
      const response = await admin.messaging().send(message);
      console.log('Successfully sent message:', response);
    } catch (error) {
      console.log('Error sending message:', error);
    }
  });

TypeScriptを使う場合

import文に変えるだけで、ロジックの修正は必要ありませんでした。

index.ts
// Cloud Functions for Firebase SDKでCloud Functionsを作成し、トリガーを設定します。
import * as functions from "firebase-functions";
// FirestoreにアクセスするためのFirebase Admin SDKです。
import * as admin from "firebase-admin";

admin.initializeApp();

exports.sendNotificationOnNewDocument = functions.firestore
  .document('notifications/{notificationId}')
  .onCreate(async (snapshot, context) => {
    const notificationData = snapshot.data();

    const message = {
      notification: {
        title: notificationData.title,
        body: notificationData.body,
      },
      token: notificationData.token,
    };

    try {
      const response = await admin.messaging().send(message);
      console.log('Successfully sent message:', response);
    } catch (error) {
      console.log('Error sending message:', error);
    }
  });

Flutterのコードを書く

必要なパッケージを追加して、コードを書きましょう。
https://pub.dev/packages/firebase_core
https://pub.dev/packages/cloud_firestore
https://pub.dev/packages/firebase_messaging
このパッケージが今回重要
通知の回数を表示してくれるアイコンボタンを作ってくれます。
https://pub.dev/packages/badges
綺麗なコードでは、ありませんがプッシュ通知の練習には、これで十分ではないかなと思われます。

main.dart
import 'package:badges/badges.dart' as badges;
import 'package:badges/badges.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

import 'firebase_options.dart';
import 'notification.dart';

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  FirebaseMessaging messaging = FirebaseMessaging.instance;
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  int _notificationCount = 0; // 通知の回数を保持する変数を追加
  bool _notificationReceived = false;
  String? _notificationMessage;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  void initState() {
    super.initState();
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got a message whilst in the foreground!');
      print('Message data: ${message.data}');

      if (message.notification != null) {
        print('Message also contained a notification: ${message.notification}');
        setState(() {
          _notificationReceived = true;
          _notificationMessage = message.notification!.body;
          _notificationCount++; // 通知が受信されたときにカウントを増やす
        });
      }
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          badges.Badge(
            badgeContent: Text(
              '$_notificationCount', // 通知の回数を表示
              style: TextStyle(color: Colors.white),
            ),
            position: BadgePosition.topEnd(top: 0, end: 3),
            child: IconButton(
              icon: Icon(Icons.notifications),
              onPressed: () {
                setState(() {
                  _notificationReceived = false;
                });
              },
            ),
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            if (_notificationMessage != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Notification message: $_notificationMessage',
                  style: TextStyle(fontSize: 20, color: Colors.red),
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => SaveNotificationPage()),
          );
        },
        tooltip: 'Save Notification',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Cloud Functionsを実行するコード

入力フォームから、タイトルとメッセージの内容を送信すると、Firestoreのnotificationsコレクションに、トークンと一緒に保存されて、トリガー関数が実行されます。

notification.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

class SaveNotificationPage extends StatefulWidget {
  
  _SaveNotificationPageState createState() => _SaveNotificationPageState();
}

class _SaveNotificationPageState extends State<SaveNotificationPage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _titleController = TextEditingController();
  final TextEditingController _bodyController = TextEditingController();

  Future<void> _saveNotification() async {
    if (_formKey.currentState!.validate()) {
      final deviceToken = await FirebaseMessaging.instance.getToken();
      CollectionReference notifications = FirebaseFirestore.instance.collection('notifications');

      await notifications.add({
        'title': _titleController.text,
        'body': _bodyController.text,
        'token': deviceToken,
      });

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Notification saved!')),
      );

      _titleController.clear();
      _bodyController.clear();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Save Notification'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _titleController,
                decoration: InputDecoration(labelText: 'Title'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a title';
                  }
                  return null;
                },
              ),
              TextFormField(
                controller: _bodyController,
                decoration: InputDecoration(labelText: 'Body'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a body';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16.0),
              ElevatedButton(
                onPressed: _saveNotification,
                child: Text('Save Notification'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

今回は、動作確認に実機が必要だったので、Androidの端末を使用しました。iPhoneも使いたかったのですが、設定が大変なので今回はやらないことにしました。
写真は、何度か撮り直しました。最初のとちょっと違います。すいません🙇‍♂️



最後に

プッシュ通知は、つけてみたいなと思っていたが、中々情報がなくて、難しいですね。試行錯誤して、色々試しながら勉強してます。
Riverpodを使ったコードもあるのですが、通知の確認ができるページでは、画面が更新されないと、確認できないようなので、ConsumerStatefulWidgetに変更して、使用しました。といってもこのページでは、Riverpodの機能は使っていなくて、次のページで使ってます。

main.dart
import 'package:badges/badges.dart' as badges;
import 'package:badges/badges.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'firebase_options.dart';
import 'notification.dart';

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  FirebaseMessaging messaging = FirebaseMessaging.instance;
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );

  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'FCM State Page'),
    );
  }
}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends ConsumerState<MyHomePage> {
  int _counter = 0;
  int _notificationCount = 0; // 通知の回数を保持する変数を追加
  bool _notificationReceived = false;
  String? _notificationMessage;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  void initState() {
    super.initState();
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got a message whilst in the foreground!');
      print('Message data: ${message.data}');

      if (message.notification != null) {
        print('Message also contained a notification: ${message.notification}');
        setState(() {
          _notificationReceived = true;
          _notificationMessage = message.notification!.body;
          _notificationCount++; // 通知が受信されたときにカウントを増やす
        });
      }
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          badges.Badge(
            badgeContent: Text(
              '$_notificationCount', // 通知の回数を表示
              style: TextStyle(color: Colors.white),
            ),
            position: BadgePosition.topEnd(top: 0, end: 3),
            child: IconButton(
              icon: Icon(Icons.notifications),
              onPressed: () {
                setState(() {
                  _notificationReceived = false;
                });
              },
            ),
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            if (_notificationMessage != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Notification message: $_notificationMessage',
                  style: TextStyle(fontSize: 20, color: Colors.red),
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => SaveNotificationPage()),
          );
        },
        tooltip: 'Save Notification',
        child: const Icon(Icons.add),
      ),
    );
  }
}
notification.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final titleProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});

final bodyProvider = StateProvider.autoDispose((ref) {
  return TextEditingController(text: '');
});

class SaveNotificationPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
    final _titleController = ref.watch(titleProvider);
    final _bodyController = ref.watch(bodyProvider);

    Future<void> _saveNotification() async {
      if (_formKey.currentState!.validate()) {
        final deviceToken = await FirebaseMessaging.instance.getToken();
        CollectionReference notifications =
            FirebaseFirestore.instance.collection('notifications');

        await notifications.add({
          'title': _titleController.text,
          'body': _bodyController.text,
          'token': deviceToken,
        });

        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Notification saved!')),
        );

        _titleController.clear();
        _bodyController.clear();
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: Text('Save Notification'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _titleController,
                decoration: InputDecoration(labelText: 'Title'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a title';
                  }
                  return null;
                },
              ),
              TextFormField(
                controller: _bodyController,
                decoration: InputDecoration(labelText: 'Body'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a body';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16.0),
              ElevatedButton(
                onPressed: _saveNotification,
                child: Text('Save Notification'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

コンソールからメッセージを送る

ターミナルに、表示されるトークンを使用すれば、端末にFCMから、メッセージを送るテストができます。


端末の履歴に通知が来てほしい

アプリの画面ではなくて、通知の履歴のところに通知が来たのを確認する方法がないかと調べていたら、こちらの記事にたどりつきました!
https://qiita.com/kazutxt/items/158843fe3fcfe5d28186

AndroidManifest.xmlに設定を追加しておく。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jboy.fcm_app">
    <application
        android:label="fcm_app"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"
            />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
            <!-- fcmのコードを追加 ここから-->
        <meta-data
            android:name="firebase_messaging_auto_init_enabled"
            android:value="false" />
        <meta-data
            android:name="firebase_analytics_collection_enabled"
            android:value="false" />
            <!-- fcmのコードを追加 ここまで-->
    </application>
</manifest>

ローカル通知の設定を追加

端末の設定を追加したが、うまくいかない?
ローカル通知の設定を追加すると、アプリを閉じていても通知が確認できた。
https://pub.dev/packages/flutter_local_notifications

pubspec.yamlに追加したパッケージ

pabspec.yaml
name: fcm_app
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=2.19.5 <3.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  firebase_core: ^2.10.0
  firebase_messaging: ^14.4.1
  firebase_analytics: ^10.2.1
  cloud_firestore: ^4.5.2
  badges: ^3.1.1
  flutter_local_notifications: ^14.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

ローカル通知を使用できるように、main.dartのコードを修正

main.dart
import 'package:badges/badges.dart' as badges;
import 'package:badges/badges.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

import 'firebase_options.dart';
import 'notification.dart';
// ローカル通知インスタンスの生成
final flnp = FlutterLocalNotificationsPlugin();

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
  // ローカル通知のコードを追加
  RemoteNotification? notification = message.notification;
  flnp.initialize(const InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher')));

  if (notification == null) {
    return;
  }
  // 通知
  flnp.show(
      notification.hashCode,
      "${notification.title}:バックグラウンド",
      notification.body,
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'channel_id',
          'channel_name',
        ),
      ));
}

Future<void> _onBackgroundMessage() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  _onBackgroundMessage();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  int _notificationCount = 0; // 通知の回数を保持する変数を追加
  bool _notificationReceived = false;
  String? _notificationMessage;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  void initState() {
    super.initState();
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got a message whilst in the foreground!');
      print('Message data: ${message.data}');

      if (message.notification != null) {
        print('Message also contained a notification: ${message.notification}');
        setState(() {
          _notificationReceived = true;
          _notificationMessage = message.notification!.body;
          _notificationCount++; // 通知が受信されたときにカウントを増やす
        });
      }
      /// [Androidの実機でアプリを閉じても表示されるようにする設定]
      RemoteNotification? notification = message.notification;
      flnp.initialize(const InitializationSettings(
          android: AndroidInitializationSettings('@mipmap/ic_launcher')));
      if (notification == null) {
        return;
      }
      // 通知
      flnp.show(
          notification.hashCode,
          "${notification.title}:フォアグラウンド",
          notification.body,
          const NotificationDetails(
            android: AndroidNotificationDetails(
              'channel_id',
              'channel_name',
            ),
          ));
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          badges.Badge(
            badgeContent: Text(
              '$_notificationCount', // 通知の回数を表示
              style: TextStyle(color: Colors.white),
            ),
            position: BadgePosition.topEnd(top: 0, end: 3),
            child: IconButton(
              icon: Icon(Icons.notifications),
              onPressed: () {
                setState(() {
                  _notificationReceived = false;
                });
              },
            ),
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            if (_notificationMessage != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Notification message: $_notificationMessage',
                  style: TextStyle(fontSize: 20, color: Colors.red),
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => SaveNotificationPage()),
          );
        },
        tooltip: 'Save Notification',
        child: const Icon(Icons.add),
      ),
    );
  }
}

動作確認をしてみたら、アプリを閉じても通知が確認できました。



Discussion