前置き
とあるアラートアプリを作成していて、ローカルでの通知機能を実装する必要が出てきました。
通知機能に関する日本語記事が何故か少ないような気がしますが、Flutter内でやることについてはこの英文記事を追っていけば問題なく進められると思います。しかし、前提としてflutter_local_notifications
というパッケージを使うために、OSごとの固有の設定が必要になるのでそれについてまとめておきます。ですので、この記事の内容は概ねflutter_local_notificationsのページのまとめです。
もし、記事の内容が参考になればTwitterのフォローをお願いします。個人的に進めているアプリ開発に関連することをよくツイートします。
更新情報
-
flutter_local_notifications
のアップデートによって,参照していた記事の内容が古くなっていたので一部修正,加筆しました.(2021/05/23)
環境
flutter_local_notificationsというパッケージを使用します。iOS, Android共にFlutterがサポートされている最小バージョンで動きます。注意事項についてはパッケージのページを参照してください。
Androidの設定
(iOSの設定はAndroidの後にあります)
通知アイコンとサウンドをカスタマイズする方法
今回はカスタマイズする必要がなければ飛ばします。必要のある方はパッケージのページを参考にしてください。
通知のスケジューリング
アプリケーションで通知をスケジュールする機能が必要な場合は、スケジュールされた通知がAlarmManager API
を使用していつ通知を表示するかを決定するため、端末が起動されたときに通知を受ける権限を要求する必要があります。その設定はAndroidManifest.xml
に書きます。
# (コメントは削除してください)
# 端末が起動されたときに通知を受ける権限を要求する
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
# ~~~
# 再起動時およびアプリケーションの更新後も通知のスケジュールを確実に維持するために必要
<receiver 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"/>
</intent-filter>
</receiver>
# ~~~
# プラグインがスケジュールされた通知の表示を処理するために必要
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
# ~~~
# (省略可)Android通知のバイブレーションパターンをカスタマイズする場合に必要
<uses-permission android:name="android.permission.VIBRATE" />
AndroidManifest.xmlのサンプル。書き方がわからない時はこれのマネをしてください。
リリース・ビルド構成(書きかけ)
Android Gradleプラグイン3.4.0以降を利用していれば、R8というツールを使って、コードの自動圧縮行ってくれますが、その際に誤って必要な部分を削除してしまうことがあるようです。一番簡単な解決方法は、flutter build apkまたはflutter build appbundleに--no-shrinkフラグを渡してR8を無効にしておけば問題は起きないはずです(調べて切れていないので、このR8を利用しない方法は良い方法なのか、あまり自信がありません。)
R8を利用する場合は、アプリケーションのリリースビルド(デバッグ機能をつけずに単純に動かすプログラム)を作成する前に、ProGuard設定ファイルをカスタマイズする必要があります。補足しておくと、ProGuardはR8の前に利用されていた圧縮ツールで、R8の設定ファイルはProguardのルールファイルと共通化されているので、先ほど追加したコンポーネントを削除しない(キープしてくれる)よう、ProGuardの設定を追加します。
-keep class com.dexterous.** { *; }
そのあと、プラグインが使用するGSON依存関係に固有の規則も追加する必要があります。また、先に示した手順で通知アイコンやサウンドをカスタマイズしている場合は、それらのリソースも削除してしまわぬようProGuard設定に追加しておく必要があリます。
公式の説明はこちら。ProGuardについて気になる方はこの記事も一読すると良いと思います。
iOSの設定
iOSプロジェクトのAppDelegate.m/AppDelegate.swift
ファイル内のdidFinishLaunchingWithOptions
メソッドに次の行を追加します。
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)
}
}
Swiftではなく、Onjective-cの場合は
if (@available(iOS 10.0, *)) {
[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}
AppDelegate.swift
が気になる方は【iOS】AppDelegate.swiftってなにしてんの?をみてください。
アプリケーションがフォアグラウンドにある間の通知の処理
iOSアプリケーションでは、フォアグラウンド(画面に表示されている状態)での通知は表示されません。
iOS 10以降では、表示オプションを使用して、アプリがフォアグラウンドにあるときに通知がトリガーされるタイミングの動作を制御します。iOSの古いバージョンでは、プラグインを初期化するために関数に渡されるインスタンスIOSInitializationSettingsオブジェクトを作成するときに、onDidReceiveLocalNotification引数に起動するメソッドを指定する一部としてコールバックを処理する必要があります。(古いバージョンに関することは参考記事をみてください: flutter_local_notifications)
一旦まとめ
ここまででAndroid, iOS共にプラグインを利用するための前設定を終えました。ここからはFlutter内で閉じているので、あとは下記の記事の通りにやっていけば大丈夫だと思います。
Local Notifications in Flutter
上の記事の内容が古くなっているため,flutter_local_notification
のページを読んだほうが良いと思います.特にExampleのコードを読むと何をやっているかがわかりやすいです.
タイムゾーンに関する補足(2021/05/23)
毎日ある時刻に通知を飛ばしたい場合はshowWeeklyAtDayAndTime
を使っていましたが,これが非推奨となったために現在はzonedSchedule
を使う必要があります.zonedScheduleではDateTime
ではなくTZDateTime
というタイムゾーンごとのDateTimeを使用するため,timezone
パッケージをインポートしておく必要があります.またtimezone
パッケージにはデバイスのタイムゾーンを取得する方法がないため,flutter_native_timezone
も追加しておきましょう.
タイムゾーンの初期化はflutterのmain()で行います.
// ...
import 'package:flutter_native_timezone/flutter_native_timezone.dart'
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
Future<void> main() async {
// ...
await _configureLocalTimeZone();
// ...
}
Future<void> _configureLocalTimeZone() async {
tz.initializeTimeZones();
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}