Flutterでローカル通知やる必要があって調べたのでメモ。
使うパッケージは、flutter_local_notifications。

基本的には↓の記事通りやりつつ、ググって情報を補完しながら進めました。

OS周りの設定
Android
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.local_notifcations">
<!-- ここから追加 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<!-- ここまで追加 -->
<application
android:label="local_notifcations"
android:icon="@mipmap/ic_launcher">
<!-- ここから追加 -->
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<!-- 2021.05.27 再起動時のローカル通知継続用設定を追加 -->
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<!-- 2021.05.27 -->
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<!-- ここまで追加 -->
<activity
android:name=".MainActivity"
通知アイコンを配置
置く場所は、android/app/src/main/res/drawable/app_icon.png。
(例)res/drawable/mipmap-hdpi/ic_launcher.png(72×72)をコピー、リネームして作成。
2021.05.27 追記
再起動時にローカル通知が消えてしまう問題があったので調査。
BOOT_COMPLETEDは、シャットダウン+電源オン時の設定で、再起動は、QUICKBOOT_POWERONを追加する必要があった。追加したら動いた。
・ローカル通知動作確認結果

※2021.07.03追記
「停止状態」での通知について、手元実機「S3-SH/Android10」ではがうまくいきましたが、「Mi MIX 2S/Android10」ではうまくいかず。アプリ起動するまで通知されない挙動になってしまいます。
色々試してみましたが改善できず。Androidは端末次第であることにご留意ください。
なんとか解決したいので情報あれば教えてください。
iOS
iOS/Runner/AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
/* ここから追加 */
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
/* ここまで追加 */
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
flutter_local_notificationsパッケージのインストール
pubspec.yaml
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
flutter_local_notifications: ^5.0.0+4 # 追加
dev_dependencies:
ローカル通知の組み込み
以降、lib/main.dartを編集する。
・パーミッション取得ダイアログの表示 ※iOSのみ
_requestIOSPerssion()を呼ばなくても後述の_initializePlatformSpecifics()を呼んだタイミングで自動で出るけど、通知サウンドのパーミッションを追加したければ自前で呼ぶ必要があるみたい。
〜〜〜
// 追加
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
〜〜〜
class _MyHomePageState extends State<MyHomePage> {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); // 追加
@override
void initState() {
super.initState();
// ここから追加
if (Platform.isIOS) {
_requestIOSPermission();
}
// ここまで追加
}
// ここから追加
void _requestIOSPermission() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
.requestPermissions(
alert: false,
badge: true,
sound: false,
);
}
// ここまで追加


初期化〜ローカル通知を単純に表示
class _MyHomePageState extends State<MyHomePage> {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
@override
void initState() {
super.initState();
_requestIOSPermission();
_initializePlatformSpecifics(); // 追加
_showNotification(); // 追加
}
〜〜〜
// ここから追加
void _initializePlatformSpecifics() {
var initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: false,
onDidReceiveLocalNotification: (id, title, body, payload) async {
// your call back to the UI
},
);
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String payload) async {
//onNotificationClick(payload); // your call back to the UI
});
}
Future<void> _showNotification() async {
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID',
'CHANNEL_NAME',
"CHANNEL_DESCRIPTION",
importance: Importance.max,
priority: Priority.high,
playSound: false,
timeoutAfter: 5000,
styleInformation: DefaultStyleInformation(true, true),
);
var iosChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
android: androidChannelSpecifics, iOS: iosChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0, // Notification ID
'Test Title', // Notification Title
'Test Body', // Notification Body, set as null to remove the body
platformChannelSpecifics,
payload: 'New Payload', // Notification Payload
);
}
// ここまで追加
〜〜〜

ローカル通知を指定日時に表示
あとで通知チャンネルIDでキャンセルすることができる。
「schedule(0, …」の部分。
@override
void initState() {
super.initState();
_requestIOSPermission();
_initializePlatformSpecifics();
//_showNotification(); // コメントアウト
_scheduleNotification(); // 追加
}
// ここから追加
Future<void> _scheduleNotification() async {
var scheduleNotificationDateTime = DateTime.now().add(Duration(seconds: 5));
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID 1',
'CHANNEL_NAME 1',
"CHANNEL_DESCRIPTION 1",
icon: 'app_icon',
//sound: RawResourceAndroidNotificationSound('my_sound'),
largeIcon: DrawableResourceAndroidBitmap('app_icon'),
enableLights: true,
color: const Color.fromARGB(255, 255, 0, 0),
ledColor: const Color.fromARGB(255, 255, 0, 0),
ledOnMs: 1000,
ledOffMs: 500,
importance: Importance.max,
priority: Priority.high,
playSound: false,
timeoutAfter: 5000,
styleInformation: DefaultStyleInformation(true, true),
);
var iosChannelSpecifics = IOSNotificationDetails(
//sound: 'my_sound.aiff',
);
var platformChannelSpecifics = NotificationDetails(
android: androidChannelSpecifics,
iOS: iosChannelSpecifics,
);
await flutterLocalNotificationsPlugin.schedule(
0,
'Test Title',
'Test Body',
scheduleNotificationDateTime,
platformChannelSpecifics,
payload: 'Test Payload',
);
}
// ここまで追加

他にもいくつか紹介があったが指定日時があれば十分だったので割愛。
- 毎日の同じ時刻
- 指定曜日の同じ時刻
- 繰り返し(毎分)
- ローカル通知表示に画像を追加
詳しくは、https://itnext.io/local-notifications-in-flutter-6136235e1b51 を参照。
予約済みのローカル通知の数を取得する
@override
void initState() {
super.initState();
_requestIOSPermission();
_initializePlatformSpecifics();
//_showNotification(); //
_scheduleNotification();
// 追加
_getPendingNotificationCount().then((value) =>
debugPrint('getPendingNotificationCount:' + value.toString()));
}
// ここから追加
Future<int> _getPendingNotificationCount() async {
List<PendingNotificationRequest> p =
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
return p.length;
}
// ここまで追加

予約済みのローカル通知をキャンセルする
通知チャンネルID指定キャンセル、全キャンセルの両方が可能。
どちらの場合もローカル通知はでなくなった。
@override
void initState() {
super.initState();
_requestIOSPermission();
_initializePlatformSpecifics();
//_showNotification(); //
_scheduleNotification();
_getPendingNotificationCount().then((value) =>
debugPrint('getPendingNotificationCount:' + value.toString()));
// 追加
_cancelNotification().then((value) => debugPrint('cancelNotification'));
}
// ここから追加
Future<void> cancelNotification() async {
await flutterLocalNotificationsPlugin.cancel(0);
}
Future<void> cancelAllNotification() async {
await flutterLocalNotificationsPlugin.cancelAll();
}
// ここまで追加

ローカル通知ダイアログをタップしたとき
初期化時に指定したonSelectNotificationが発火。
その際、通知をセットしたときのpayload値が渡る。
await flutterLocalNotificationsPlugin.schedule(
0,
'Test Title',
'Test Body',
scheduleNotificationDateTime,
platformChannelSpecifics,
payload: 'Test Payload',
);
}
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String payload) async {
debugPrint(
'flutterLocalNotificationsPlugin.initialize.' + payload.toString());
//onNotificationClick(payload); // your call back to the UI
});

ここまでで、Flutterでローカル通知は大体わかったので自分のアプリ「来期アニメ」に組み込みました。
最後に全文。
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
@override
void initState() {
super.initState();
if (Platform.isIOS) {
_requestIOSPermission();
}
_initializePlatformSpecifics();
//_showNotification();
_scheduleNotification();
_getPendingNotificationCount().then((value) =>
debugPrint('getPendingNotificationCount:' + value.toString()));
//_cancelAllNotification().then((value) => debugPrint('cancelNotification'));
}
void _requestIOSPermission() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
.requestPermissions(
alert: false,
badge: true,
sound: false,
);
}
void _initializePlatformSpecifics() {
var initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: false,
onDidReceiveLocalNotification: (id, title, body, payload) async {
debugPrint('initializationSettingsIOS:onDidReceiveLocalNotification.');
},
);
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String payload) async {
debugPrint(
'flutterLocalNotificationsPlugin.initialize.' + payload.toString());
});
}
Future<void> _showNotification() async {
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID',
'CHANNEL_NAME',
"CHANNEL_DESCRIPTION",
importance: Importance.max,
priority: Priority.high,
playSound: false,
timeoutAfter: 5000,
styleInformation: DefaultStyleInformation(true, true),
);
var iosChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
android: androidChannelSpecifics, iOS: iosChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0, // Notification ID
'Test Title', // Notification Title
'Test Body', // Notification Body, set as null to remove the body
platformChannelSpecifics,
payload: 'New Payload', // Notification Payload
);
}
Future<void> _scheduleNotification() async {
var scheduleNotificationDateTime = DateTime.now().add(Duration(seconds: 2));
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID 1',
'CHANNEL_NAME 1',
"CHANNEL_DESCRIPTION 1",
icon: 'app_icon',
//sound: RawResourceAndroidNotificationSound('my_sound'),
//largeIcon: DrawableResourceAndroidBitmap('app_icon'),
enableLights: true,
color: const Color.fromARGB(255, 255, 0, 0),
ledColor: const Color.fromARGB(255, 255, 0, 0),
ledOnMs: 1000,
ledOffMs: 500,
importance: Importance.max,
priority: Priority.high,
playSound: false,
timeoutAfter: 10000,
styleInformation: DefaultStyleInformation(true, true),
);
var iosChannelSpecifics = IOSNotificationDetails(
// sound: 'my_sound.aiff',
);
var platformChannelSpecifics = NotificationDetails(
android: androidChannelSpecifics,
iOS: iosChannelSpecifics,
);
await flutterLocalNotificationsPlugin.schedule(
0,
'Test Title',
'Test Body',
scheduleNotificationDateTime,
platformChannelSpecifics,
payload: 'Test Payload',
);
}
Future<int> _getPendingNotificationCount() async {
List<PendingNotificationRequest> p =
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
return p.length;
}
Future<void> _cancelNotification() async {
await flutterLocalNotificationsPlugin.cancel(0);
}
Future<void> _cancelAllNotification() async {
await flutterLocalNotificationsPlugin.cancelAll();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('local notifications test'),
),
body: Center(child: Text('test')),
);
}
}
お役に立てたようであればぜひ自作アプリのダウンロードをお願いします。
コメント