[2020年] Unityでローカルプッシュ通知を行う最小設定

## はじめに
本記事はUnityで手軽にローカルプッシュ通知を行いたい人向けの記事です。
なお本内容はUnity公式の[Unity Mobile Notifications Package](https://docs.unity3d.com/Packages/com.unity.mobile.notifications@1.0/manual/)を参考に書いております。こちらの記事をみてもうまくできない人に読んでもらえればと思います。

※記事の中には一部分公式と違う点がありますが、その点は公式でも本記事の通りに行ってもどちらでも動くと思います。

## Unity Mobile Notifications Packageのインストール
まずは`Unity Mobile Notifications Package`をインストールしましょう。
<img width="222" alt="push1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79216/7f34b80b-4587-f67c-ed16-4ca13203a59e.png">
Unityの上部のメニューの`Window->Package Manager`を開きます。
<img width="697" alt="スクリーンショット 2020-03-09 10.24.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79216/31ec4939-12f1-2a2a-3d6c-eb1159e333ed.png">
その後で`Mobile Notifications`をタップして右下の`install`をタップします。
※上記はインストール後の画像です。

### 注意
`Mobile Notifications`が表示されない方は左上のパッケージ設定が`In Projects`や`My Assets`になっていると表示されない場合があります。
その場合は`All Packeges`に変更して探してください。

## プロジェクト設定
上記のパッケージをインストールすれば使えますが、最低限の設定として以下を行います。
<img width="225" alt="スクリーンショット 2020-03-09 10.25.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79216/0bdb5ba8-30ff-438e-d085-1eaa6043d836.png">
`Edit -> Project Settings...`を開きます。

### Androidのアイコン設定
Androidでは通知を行うアイコンの設定ができます。
iOSも出来るよう(未確認)ですが、iOSではデフォルトでアプリアイコンが使用されるので僕はそのままにしました。
<img width="961" alt="スクリーンショット 2020-03-09 10.25.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79216/4d13c2a4-7ede-bd0e-1435-69455b92bcc2.png">
`Mobile Notification Settings`を開きます。
そして、上のタブは`Android`を指定します。（はじめに開かれているはずです。）

ここで二つのアイコンを設定します。
アイコンはAndroidの上部に表示される`Small Icon`と通知本文に表示される`Large Icon`を複数設定できます。
`Small Icon`は`48x48`のサイズで設定する必要があり、透過部分がくり抜かれます。
(上記の画像のプレビューが白いのは透過部分が無いためです。)

`Large Icon`は`192x192`のアイコンを設定します。
ここで指定した`Identifier`はあとでプログラムで使用します。

### iOSの設定
iOSではプッシュ通知の許可が必要になります。
`iOS`のタブに変更します。
<img width="960" alt="スクリーンショット 2020-03-09 10.25.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79216/cf2c9a42-a051-a228-a353-96fe18e7b774.png">
上記の丸が付いているチェックボックスにチェックを入れます。

iOSでは任意のタイミングで通知の許可を行えるのですが、`Mobile Notifications`では起動時のみの設定しかできないようです。
（この記事をみている人ならこの設定で十分だと思います。）

## 実装
以下のプログラムをプロジェクトに追加してください。

```C#:LocalPushNotification
#if UNITY_ANDROID
using Unity.Notifications.Android;
#endif
#if UNITY_IOS
using Unity.Notifications.iOS;
#endif
using System;

/// <summary>
/// ローカルプッシュ通知送信クラス
/// </summary>
public static class LocalPushNotification
{
    /// <summary>
    /// Androidで使用するプッシュ通知用のチャンネルを登録する。
    /// </summary>
    public static void RegisterChannel(string cannelId, string title, string description)
    {
#if UNITY_ANDROID
        // チャンネルの登録
        var c = new AndroidNotificationChannel()
        {
            Id = cannelId,
            Name = title,
            Importance = Importance.High,
            Description = description,
        };
        AndroidNotificationCenter.RegisterNotificationChannel(c);
#endif
    }

    /// <summary>
    /// 通知をすべてクリアーします。
    /// </summary>
    public static void AllClear()
    {
#if UNITY_ANDROID
        // Androidの通知をすべて削除します。
        AndroidNotificationCenter.CancelAllScheduledNotifications();
        AndroidNotificationCenter.CancelAllNotifications();
#endif
#if UNITY_IOS
        // iOSの通知をすべて削除します。
        iOSNotificationCenter.RemoveAllScheduledNotifications();
        iOSNotificationCenter.RemoveAllDeliveredNotifications();
        // バッジを消します。
        iOSNotificationCenter.ApplicationBadge = 0;
#endif
    }

    /// <summary>
    /// プッシュ通知を登録します。
    /// </summary>
    /// <param name="title">通知のタイトル</param>
    /// <param name="message">通知メッセージ</param>
    /// <param name="badgeCount">表示するバッジ数</param>
    /// <param name="elapsedTime">何秒後に表示させるか？</param>
    /// <param name="cannelId">Androidで使用するチャンネル</param>
    public static void AddSchedule(string title, string message, int badgeCount, int elapsedTime, string cannelId)
    {
#if UNITY_ANDROID
        SetAndroidNotification(title, message, badgeCount, elapsedTime, cannelId);
#endif
#if UNITY_IOS
        SetIOSNotification(title, message, badgeCount, elapsedTime);
#endif
    }

#if UNITY_IOS
    /// <summary>
    /// 通知を登録します。(iOS)
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    /// <param name="badgeCount"></param>
    /// <param name="elapsedTime"></param>
    static private void SetIOSNotification(string title, string message, int badgeCount, int elapsedTime)
    {
        // 通知を作成します。
        iOSNotificationCenter.ScheduleNotification(new iOSNotification()
        {
            // ※プッシュ通知を個別に取り消しなどをする場合はこのIdentifierを使用します。
            Identifier = $"_notification_{badgeCount}",
            Title = title,
            Body = message,
            ShowInForeground = false,
            Badge = badgeCount,
            Trigger = new iOSNotificationTimeIntervalTrigger()
            {
                TimeInterval = new TimeSpan(0, 0, elapsedTime),
                Repeats = false
            }
        });
    }
#endif

#if UNITY_ANDROID
    /// <summary>
    /// 通知を登録する。(Android)
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    /// <param name="badgeCount"></param>
    /// <param name="elapsedTime"></param>
    /// <param name="cannelId"></param>
    static private void SetAndroidNotification(string title, string message, int badgeCount, int elapsedTime, string cannelId)
    {
        // 通知を作成します。
        var notification = new AndroidNotification
        {
            Title = title,
            Text = message,
            Number = badgeCount,
            // ※ここでAndroidのアイコンを設定します。
            SmallIcon = "icon_small",
            LargeIcon = "icon_large",
            FireTime = DateTime.Now.AddSeconds(elapsedTime)
        };

        // 通知を送信します。
        AndroidNotificationCenter.SendNotification(notification, cannelId);
        // ※以下のコードを使うことで個別にプッシュ通知の制御ができます。
        //var identifier = AndroidNotificationCenter.SendNotification(notification, cannelId);
        //UnityEngine.Debug.Log($"TownSoftPush: プッシュ通知の登録完了 -> {DateTime.Now.AddSeconds(elapsedTime)}");

        //if (AndroidNotificationCenter.CheckScheduledNotificationStatus(identifier) == NotificationStatus.Scheduled)
        //{
        //    // Replace the currently scheduled notification with a new notification.
        //    UnityEngine.Debug.Log("プッシュ通知はすでに登録済み");
        //}
        //else if (AndroidNotificationCenter.CheckScheduledNotificationStatus(identifier) == NotificationStatus.Delivered)
        //{
        //    //Remove the notification from the status bar
        //    //AndroidNotificationCenter.CancelNotification(identifier);
        //    UnityEngine.Debug.Log("プッシュ通知はすでに通知済み");
        //}
        //else if (AndroidNotificationCenter.CheckScheduledNotificationStatus(identifier) == NotificationStatus.Unknown)
        //{
        //    //AndroidNotificationCenter.SendNotification(newNotification, "channel_id");
        //    UnityEngine.Debug.Log("プッシュ通知は不明な状況");
        //}
    }
#endif
}
```
上記のコード内に`※`で注釈している部分を必要に応じて変更してください。

## 使用方法
Androidでは事前に通知チャンネルを登録する必要があります。
なので、起動時に以下のコードを呼び出します。
※以下のコードはiOSで呼び出しても何も処理を行わないのでプラットフォームによって分ける必要はありません。

```C#
// チャンネルの登録
LocalPushNotification.RegisterChannel("channelId", "アプリ名（チャンネル名)", "説明");
```

その後、プッシュ通知を登録したい場合は以下のコードを呼び出してください。

```C#
// プッシュ通知の登録
LocalPushNotification.AddSchedule("プッシュ通知タイトル", "内容", 1, 60, "channelId");
```
左から`プッシュ通知タイトル`、`プッシュ通知に表示する内容`、`バッチ番号`、`通知時間（指定秒後に通知します）`、`チャンネル登録したID`を指定します。
僕の端末のせいか、バッチ番号はAndroidでは表示されませんでした。
チャンネル登録したIDは`上記コードでチャンネル登録したID`を指定します。上記の例では`channelId`になります。

プッシュ通知を削除したい場合は以下のコードを呼び出してください。
バッチ番号も消えます。

```C#
LocalPushNotification.AllClear();
```

## ハマった点
僕はiOSユーザーなので、Androidのことを知らずに上記を実装していました。
その時に通知が全く来なかったのでなんでだろう？と思って調べてたらマナーモードでは通知が来ないんですね・・・。
恥ずかしいことですが、これでしばらくハマってました。

## 上級者へ
上記コードはプッシュ通知を送信して消すことしかできません。
しかし、`Mobile Notifications`には`Identifier`が用意されていますので上記コードを改修することで個別の通知削除や編集なども出来るようになります。

その際にはiOSとAndroidの差異を吸収する必要があるので注意してください。
iOSでは`Identifier`は文字列ですが、Androidでは数字になっています。

## 最後に
個人でアプリを作っている場合はプッシュ通知の敷居がとても高い（証明書の用意、通知サーバーやの用意など）です。
しかし、ローカルプッシュ通知の場合はあまり敷居が高くないので積極的に使っていくとユーザーの呼び戻しができます。

個人でアプリを作っている人のお役に立てればと思います。