【Flutter】flutter_local_notificationsで定期的に通知する
概要
定期的にアプリからローカル通知を行う実装を試してみたいと思います。
使うパッケージ
サポートされるプラットフォーム
- Android 4.1以上
- NotificationCompat APIを使用しているため、古いAndroidデバイスでも動作可能らしい
- iOS 8.0以上
- iOSのバージョンが10より古い場合
- UILocalNotification APIを使用
- iOS 10以降
- UserNotification API(別名User Notifications Framework)を使用
- iOSのバージョンが10より古い場合
パッケージインストール
dependencies:
flutter_local_notifications: ^17.1.2
Android Setup
基本的には公式ドキュメント通りに進めていきます。
-
android/app/build.gradle
に以下を追加-
Androidの古いバージョンでの後方互換性を持つスケジュール通知をサポートするためにdesugaring に依存するようになったらしい
-
desugaringとは?
- 古いAndroidバージョンで新しいJava言語機能とAPIを使用可能にするプロセス
- Android Gradle Plugin 4.0.0以降でサポート
android { defaultConfig { multiDexEnabled true } compileOptions { // Flag to enable support for the new language APIs coreLibraryDesugaringEnabled true // Sets Java compatibility to Java 8 sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' }
-
-
android/build.gradle
を以下に修正buildscript { ... dependencies { classpath 'com.android.tools.build:gradle:7.3.1' ... }
-
android/app/src/main/AndroidManifest.xml
に以下を追加<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application ... <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" /> <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> </intent-filter> </receiver> </application>
iOS Setup
-
ios/Runner/AppDelegate.swift
に以下を追加if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate }
Permissionリクエスト
以下の requestPermissions
を呼ぶ事で通知許可のダイアログを表示させます。
Future<void> requestPermissions() async {
if (Platform.isIOS) {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
} else if (Platform.isAndroid) {
// Android 13 (API レベル 33) 以降で必要
final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await androidImplementation?.requestNotificationsPermission();
}
}
アプリがフォアグラウンドの時の処理
iOS10以降の場合、基本foregroundの通知は表示されませんが、flutter_local_notificationsではデフォルトでforeground通知を表示してくれます。
古いiOSの場合 DarwinInitializationSettings
の onDidReceiveLocalNotification
で処理を書く必要があります。
通知タップ時のハンドリングは onDidReceiveNotificationResponse
で処理します。
ボタンをタップしたら通知を表示
まずはシンプルにボタンをタップしたら通知が表示されるように実装してみたいと思います。
以下の様なメソッドを作成し、ボタンタップしたらメソッド呼ぶようにします。
Future<void> showNotification() async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails('your channel id', 'your channel name',
channelDescription: 'your channel description',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');
const NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.show(
0, 'plain title', 'plain body', notificationDetails,
payload: 'item x');
}
実行すると↓の様に通知が表示される様になります。
iOS
Android
実装部分の詳細を見てみたいと思います。
- AndroidNotificationDetails
ちなみに channelId
, channelName
, channelDescription
は Android 8.0(API レベル 26)以降の端末だとアプリの設定画面で確認する事ができます。
5秒後に通知を表示
次にボタンをタップして5秒後に通知が表示されるようにしてみたいと思います。
flutter_local_notifications
で時間を扱う一緒にインストールされるtimezoneを使います。
最初にtimezoneパッケージの初期化を行います。Locationも Asia/Tokyo
に設定しときます。
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/standalone.dart' as tz;
void main() async{
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation("Asia/Tokyo"));
// ....
}
次に以下メソッドを作成し、ボタンがタップされたら呼ぶようにします。
Future<void> scheduleNotification() async {
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
'scheduled title',
'scheduled body',
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
const NotificationDetails(),
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
}
↓実際に試した結果5秒後に通知がきています。
毎日特定の時刻に通知を表示
次に特定の時刻に通知が来るようにしたいと思います。
↓の everyScheduleNotification
を呼ぶと9:00に通知が来るような実装になってます。
Future<void> everyScheduleNotification() async {
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
'scheduled title',
'scheduled body',
_scheduledDateAtHour(9),
const NotificationDetails(),
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
}
tz.TZDateTime _scheduledDateAtHour(int hour) {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate =
tz.TZDateTime(tz.local, now.year, now.month, now.day, hour);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
void cancelAllNotifications() async {
await flutterLocalNotificationsPlugin.cancelAll();
}
通知のキャンセルは cancelAllNotifications
を呼ぶことで今回は全部の通知をキャンセルします。個別にキャンセルする場合は通知IDを保持しておいて個別にキャンセルします。
バッドノウハウ
Androidで通知時に以下エラーが出る
PlatformException (PlatformException(error, Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference, null, java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference
↑にある通り AndroidNotificationDetails
の icon
を設定、または AndroidInitializationSettings
でデフォルトの通知アイコンを設定します。
AndroidNotificationDetails('your channel id', 'your channel name',
channelDescription: 'your channel description',
icon: "@mipmap/ic_launcher", // ← ここ
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');
// 又は
flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
),
);
Androidで通知設定時に以下エラーが出る
Unhandled Exception: PlatformException(exact_alarms_not_permitted, Exact alarms are not permitted, null, null)
AndroidManifest.xml
に以下を追加し、
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
次に、以下のパーミッションの要求を追加します。
await androidImplementation?.requestExactAlarmsPermission();
参考URL
Discussion