FlutterでローカルからNotification

# はじめに
Push通知ではなくアプリケーション上から通知を出すためにどうするのか調べました。

# 環境
Android Studio
Flutter1.0

今回はiOSを表示させる環境用意しなかったので、Androidのみに言及しています。
iOSだと動かない箇所が出てくると思います。

# Dependencyの追加
Flutterには通知を簡単に実現するための`flutter_local_notifications`が用意されているので、それを`pubspec.yaml`に追加してあげます。
バージョンがまだ0.5.1なので、バージョンアップ後に動かなくなる可能性がありますので、この先読む方はご注意ください。

``` yaml
dependencies:
  flutter_local_notifications: ^0.5.1
```

# アプリ側のトリガーを元に通知を行う
アプリ側のイベントを契機に通知を行う方法について見ていきます。

まずは必要となるpackageをimportしてあげます。

``` Dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
```

次からは`State<T>`内部に実装する必要があるため、必要な実装は先に行なっておきます。
私は`Flutter`を勉強しながら継ぎ足し製法でソースを拡充させているため、それを使います。
なお、色んなコードが入ってきてしまっているため、本ページでは全体のコードはのせませんがご了承ください。

`State<T>`の実装が完了したらメンバ変数として`FlutterLocalNotificationsPlugin`を追加します。
様々なサンプルだとメンバ変数になってませんが、これを使って実際の通知を行う必要があるため、メンバ変数としないと無理です。

``` Dart
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
NotificationDetails platformChannelSpecifics;

```

まずは通知を行うための初期化処理を行います。
初期化は`State<T>`の`initState`内に実装します。
ここでの注意点はサンプルをコピーしただけではエラーとなるため、実装が必要になる部分があることです。

また、実際の通知する際に使用するオブジェクトもここで生成しておいた方が良いです。
`AndroidNotificationDetails`のコンストラクタはAndroid8.0以上のチャンネル機能用の通知用があるので、8.0以上を目的で作る場合は必須となります。

``` Dart
  @override
  void initState() {
    super.initState();
    _refresh();

    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    var initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
    var initializationSettingsIOS = IOSInitializationSettings(
        onDidReceiveLocalNotification: onDidReceiveLocationLocation);
    var initializationSettings = InitializationSettings(
        initializationSettingsAndroid, initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);

    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max, priority: Priority.High);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
  }
```

そして必要な箇所を追加します。
`onDidReceiveLocationLocation`はiOS用のようなので、本ページに必要ない実装ですがなぜか実装してあったので、合わせてのせておきます。

`onSelectNotification`についてが今回重要な点となります。
このメソッドは通知をクリックした際に呼ばれるイベントとなっているため、その動作について実装が必要となります。

今回は`Navigator.push`を使用して別の`Widget`を呼び出してます。
そもそもNotificationからの起動時は今の画面の次の画面として起動するか、一から起動してから次の画面として起動するかという動作をする感じなので、`Future.sync`などから`Widget`を作っても現状では動作がどう変わるかが良くわかりませんでした。

何かパラメータを渡す際は引数の`payload`を使って行う必要があります。
ここは`String`固定なので、複雑なデータを渡す際は`Json`なんかで渡す必要があります。

``` Dart
  Future onSelectNotification(String payload) async {
    await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => Text(payload),
        maintainState : false,),
    );
  }

  Future onDidReceiveLocationLocation(
      int id, String title, String body, String payload) async {
    await showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text(title),
            content:  Text(body),
            actions: <Widget>[
              FlatButton(
                child: Text(payload),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        }
    );
  }
```

最後に実際に通知を行う箇所です。
`initState`で生成したオブジェクトを使用して通知します。
`onSelectNotification`に渡ってくる`payload`は`push`の`payload`で設定したものと同じものなので、通知後にパラメータを渡したい場合はここで設定してあげてください。

``` Dart
  Future _onNotification() async {
    await flutterLocalNotificationsPlugin.show(
        0, 'plain title', 'plain body', platformChannelSpecifics,
        payload: 'item id 2');
  }
```

これで通知できるようになりました。

## 通知をスケジューリングする
アプリケーショントリガーですぐに実行することが出来るようになりましたが、そのトリガーを元にすぐに出さずにあとで出すようにしたい場合にスケジューリングさせることが出来ます。
その場合にどうするべきかを見ていきます。

### AndroidManifest.xmlの修正
スケジューリングされた通知を行うのに`AlarmManager`使用しているらしく、それが使用できるようにManifestを修正してあげる必要があります。
修正内容は公式書いてあるので、その通りに追加してあげます。

`AlermManager`を使用している場合はアプリが起動していなくても起動してくれるので、停止状態でも出せるようになります。

なお、`com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver`を追加するとAndroid Studio上でエラーになりますが、特に問題ないので無視してください。

``` 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"/>
    </intent-filter>
</receiver>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
```

### dartファイル
コード的には`schedule`メソッドを使用して、時間を指定あげるだけの違いとなります。

``` Dart
  Future _onNotification() {
    return flutterLocalNotificationsPlugin.schedule(
        0, 'plain title', 'plain body', DateTime.now().add(Duration(seconds: 5)),
        platformChannelSpecifics, payload: 'item id 2');
  }
```

## 繰り返し通知
Manifestの修正はしておいてください。
コード的には`periodicallyShow`を使えば実現できます。
指定する`RepeatInterval`はenumなので、簡単に繰り返す期間の指定が可能です。

``` Dart
  Future _onNotification() {
    return flutterLocalNotificationsPlugin.periodicallyShow(
        0, 'plain title', 'plain body', RepeatInterval.EveryMinute,
        platformChannelSpecifics, payload: 'item id 2');
  }
```

### 特定の時間で繰り返す
毎日定時に起動させたい場合は`showDailyAtTime`を使用します。

``` Dart 
  Future _onNotification() {
    var date = DateTime.now().add(Duration(seconds: 5));
    return flutterLocalNotificationsPlugin.showDailyAtTime(
        0, 'plain title', 'plain body', Time(date.hour, date.minute, date.second),
        platformChannelSpecifics, payload: 'item id 2');
  }
```

### 特定の曜日、特定の時間で繰り返す
特定の曜日も追加したい場合は`showWeeklyAtDayAndTime`で曜日と時間を指定します。

``` Dart
  Future _onNotification() {
    var date = DateTime.now().add(Duration(seconds: 5));
    return flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
        0, 'plain title', 'plain body', Day.Sunday, Time(date.hour, date.minute, date.second),
        platformChannelSpecifics, payload: 'item id 2');
  }
```

# おわりに
本当は起動時にNotificationを設定する方法も合わせて見ていたのですが、没としました。
今の所は状態変化のタイミングに合わせて通知設定をするというのが一番良いです。
面倒だから起動と同時に設定できるようにしたいなーと思ってたんですが、しょうがないので別の方法考えます。