ExpoでのNotificationの各機能について検証してみたのをまとめます。
Notificationの流れがある程度わかっている方向けの記事になりますので、悪しからずご容赦ください。
環境
Expo v35.0
iOS 13/iPhone8
Android9/AQUOS sense2 SHV43
Expoプッシュトークンを取得
ひとまずトークンを取得して、コンソールに表示してみます。(ほぼ公式のソースのまま)
import { Notifications } from "expo";
import * as Permissions from "expo-permissions";
(async () => {
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
let finalStatus = existingStatus;
// only ask if permissions have not already been determined, because
// iOS won't necessarily prompt the user a second time.
if (existingStatus !== "granted") {
// Android remote notification permissions are granted during the app
// install, so this will only ask on iOS
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
finalStatus = status;
}
// Stop here if the user did not grant permissions
if (finalStatus !== "granted") {
return;
}
// Get the token that uniquely identifies this device
const token = await Notifications.getExpoPushTokenAsync();
console.log("token!!!", token);
})();
ExponentPushToken
から始まるトークンがコンソールに表示されたらOKです。
token!!! ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]
このトークンは端末ごとに固有のもので、アプリをインストールし直したりした場合には新しく生成されます。
ですので、実際にアプリで使用する場合はユーザーごとに複数のトークンを保存するような仕組みが必要になります。
送信してみる
では、試しにNotificationを送信してみましょう。
公式のPush Notification Tool
やサーバー用のSDKも各種揃っていますが、要はExpoのAPIにトークンやNotificationのデータをPOSTしているだけなので、以下のように簡単にターミナルからテストすることができます。
$ curl -H "Content-Type: application/json" -X POST "https://expo.io/--/api/v2/push/send" -d '{
"to": "ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]",
"title":"hello",
"body": "world",
"data": {},
}'
to
のところに先ほど取得したExpoプッシュトークンをいれます。APIを叩いてエラーがなければ端末に通知が届くと思います。
Expo Clientで開発している場合、アプリ↔︎OS間のやりとりはExpo Clientを介すことになるので、特にNotificationに関してはスタンドアロンアプリと違う挙動をすることが多いです。
iOS/Androidで試しにそれぞれ送ってみましょう。
iOS
上:ExpoClient上のアプリで取得したトークンに送ったもの
下:Expoでスタンドアロンビルドされたアプリに送ったもの
こちらはアイコン以外に変化ないですね。ExpoClientの場合はExpoClientのアイコンが表示され、スタンドアロンアプリの場合はapp.jsonで指定されたアプリ自体のアイコンが表示されます。
一方、
Android
上:ExpoClient上のアプリで取得したトークンに送ったもの
下:Expoでスタンドアロンビルドされたアプリに送ったもの
ExpoClientの場合はタイトルが[アプリ名] - [Notificationタイトル]
というように表示されました。これはExpoClient側が勝手にやってるものと思われます。また、スタンドアロンアプリの場合はアプリアイコンが表示されていません。これはフォーラムにもissueが上がっているようで、Expo側の問題かもしれません。
https://forums.expo.io/t/push-notification-dynamic-icon/1684/5
小さいアイコンはどちらもExpoのデフォルトのもの(上向きの矢印のような)が表示されていますね。
AndroidのNotificationアイコンをカスタマイズする
小さいアイコンに関してはapp.json
での設定が必要なようです。
ドキュメントを参考にしてみます。
assets/notificationicon.png
説明通り、96px x 96pxで透過PNGの画像を作成しました。このページでは白く見える部分が透過されています。
これをapp.json
で指定します。
"notification": {
"icon": "./assets/notificationicon.png",
"color": "#00c2ad"
},
アプリをビルドし、Notificationを送ってみると...
小さいアイコンが設定したものに変わりました。もっとシンプルなアイコンにした方が良さそうです。
説明だとcolor
で指定した色がオーバーレイされるはずなのですが、色は他のUIと共通のものになっていますね。これは端末のソフトウェアによるのかもしれません。
ひとまずExpoのアイコンではなくなったので良しとしましょう。
追記
端末によっては、ここで指定したアイコンがそのままの色で表示されることがあるようです。
白い透過画像に差し替えると
こんな感じに馴染みました。白色・透過PNGがベストかもしれません。
Androidでは通知の表示のされ方は端末ごとのソフトウェアによって結構ばらつきがあるようです。
バッジを制御してみる
アプリアイコンに表示されるバッジの数字を動的に制御してみます。
APIにbadge
パラメータを渡すと...
$ curl -H "Content-Type: application/json" -X POST "https://expo.io/--/api/v2/push/send" -d '{
"to": "ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]",
"title":"hello",
"body": "world",
"data": {},
"badge": 100
}'
iOSではアプリ側でもバッジの制御ができます。
数字を0に戻す処理を足してみます。
import { Platform } from 'react-native';
if (Platform.OS === 'ios') {
Notifications.setBadgeNumberAsync(0);
}
バッジが消えました。
ユーザーが通知に関わるコンテンツを閲覧したらバッジの数を変更するといったことがこれで可能です。(現在のバッジの数はNotifications.getBadgeNumberAsync()
で取得できます。)
アプリ側で通知に反応する
通知が届いたときにアプリ側で処理するために、リスナーを登録してみます。
import { Notifications } from "expo";
Notifications.addListener(notification => {
console.log('received notification', notification);
});
リスナーに渡されたNotificationの内容をコンソールに出してみると、こんな感じです。
remote
はPushNotificationかLocalNotificationかどうかが、
actionId
とuserText
はあとで紹介するNotificationCategoryのときに値が入ります。
origin
はアプリへの導線を表し、selected
の場合は通知をタップしたとき、received
の場合はアプリ起動中に通知が来たときです。
これをみることで、例えば通知をタップしたら詳細画面へ直接遷移し、アプリ起動中の場合はアラートダイアログを出したあと詳細画面へ遷移する、といった制御が可能です。
import { Alert } from "react-native";
...
if (notification.origin === "selected") {
// 詳細画面へ
} else if (notification.origin === "received") {
Alert.alert(
`コメントが届きました`,
"詳細画面へ移動しますか?",
[
{
text: "キャンセル",
style: "cancel"
},
{
text: "移動する",
onPress: () => {
// 詳細画面へ
}
}
]
);
}
スタンドアロンアプリとしてビルドして動作検証済みです。
ExpoClientでの開発中は通常、通知をタップしたらExpoClientのホーム画面に移動してしまうので、selected
のパターンは確認できません。
また、アラートを表示するなどしなくても、APIに_displayInForeground
パラメータをつけることでアプリ起動中にも上に通知が表示されるようになります。
$ curl -H "Content-Type: application/json" -X POST "https://expo.io/--/api/v2/push/send" -d '{
"to": "ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]",
"title":"hello",
"body": "world",
"data": {},
"_displayInForeground": true
}'
iOSでは、
アプリの上に重なります。この状態で通知をタップしたらreceived
に。
通知が上に引っ込んでから、通知センターを引き出して、通知をタップしたらselected
に。
あるいは
アプリに重なっている通知を深くタップしたり下にスワイプしてフォーカスしてからタップするとselected
になります。
Androidでは残念ながら_displayInForeground
は動作しませんでした。
また、iOSでは_displayInForeground
ではない通知がアプリ起動時に届いた場合通知センターに通知が表示されないのに対して、
Androidでは_displayInForeground
やアプリの起動状態に関係なく通知センターに表示されます。アプリが起動中ならその瞬間received
でリスナーが呼ばれ、その通知をタップした場合にもselected
でリスナーが呼ばれるので、Androidでは一つの通知に関して最大2回リスナーが呼ばれることになります。
NotificationCategoryを使ってみる
iOSではInteractiveNotifications、Androidでは通知アクションといわれる機能を使って、ユーザーが通知に対するアクションを選択できるようにしてみます。
まずはアプリ側で通知カテゴリ(Notification Category)を定義します。
import { Notifications } from "expo";
Notifications.createCategoryAsync('myCategory', [
{
actionId: "foo",
buttonTitle: "foo", // ボタンに表示されるタイトル
textInput: { // テキスト入力をさせる場合
submitButtonTitle: 'OK', // 送信ボタンのtaitoru
placeholder: 'placeholder' // 未入力の時に表示されるplaceholder
},
isDestructive: true, // trueだとボタンが赤文字になる(iOSのみ)
isAuthenticationRequired: true // trueだとロック時にアクションできない(iOSのみ)
},
{
actionId: "bar",
buttonTitle: "bar",
isDestructive: false,
isAuthenticationRequired: false
},
{
actionId: "baz",
buttonTitle: "baz",
isDestructive: false,
isAuthenticationRequired: false,
doNotOpenInForeground: true // trueにするとタップしてもアプリを開かない(iOSのみ)
}
]);
Notifications.createCategoryAsync
の第一引数には任意のカテゴリーIDを、第二引数にアクションの配列を渡します。
アクションのactionId
は先ほどのリスナーに渡されるNotificationオブジェクトに入るので、これによってアプリ側からはどのアクションが呼ばれたか判別できるというわけです。
アプリ側で一度このコードを実行しておいてから、APIを叩いてPush通知を送信してみましょう。
$ curl -H "Content-Type: application/json" -X POST "https://expo.io/--/api/v2/push/send" -d '{
"to": "ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]",
"title":"hello",
"body": "world",
"data": {
"some": "data"
},
"_category": "@user/experienceId:myCategory"
}'
_category
パラメータに先ほどアプリ側で定義したカテゴリーIDをいれます。
ExpoClient上で確認する場合は、@ユーザー名/プロジェクト名:カテゴリーID
というようにprefixをいれて送ります。
スタンドアロンアプリではカテゴリーIDのみで大丈夫です。
通知が届き、iOSでは通知を深くタップするとこのようにボタンが表示されました。
一つ目のfoo
はテキストを入力させるように定義しているので、タップすると入力UIが表示されます。
これで送信ボタンをタップしたらアプリ側のリスナーが呼ばれます。
入力された内容は先ほどのuserText
から参照できます。
Androidでは...
通知センターにそのまま表示されました。
テキスト入力もできます。送信ボタンのタイトル(submitButtonTitle
)は効かず、アイコンになっていますね。
あまり使わないでしょうが、カテゴリーを削除するためのメソッドも用意されています。
Notifications.deleteCategoryAsync(categoryId)
終わり
以上、ExpoのNotificationについて、使いそうな機能を検証してみました。
引き続きほかの機能も調べていこうと思います。
追記
各機能をExpoClient/スタンドアロンビルド/ストアからインストールのそれぞれで検証してみた状況をスプレッドシートにまとめています。