Help us understand the problem. What is going on with this article?

Expo/Notificationの各機能を使ってみる

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でスタンドアロンビルドされたアプリに送ったもの
PNGイメージ.png
こちらはアイコン以外に変化ないですね。ExpoClientの場合はExpoClientのアイコンが表示され、スタンドアロンアプリの場合はapp.jsonで指定されたアプリ自体のアイコンが表示されます。

一方、

Android
上:ExpoClient上のアプリで取得したトークンに送ったもの
下:Expoでスタンドアロンビルドされたアプリに送ったもの
Android
ExpoClientの場合はタイトルが[アプリ名] - [Notificationタイトル]というように表示されました。これはExpoClient側が勝手にやってるものと思われます。また、スタンドアロンアプリの場合はアプリアイコンが表示されていません。これはフォーラムにもissueが上がっているようで、Expo側の問題かもしれません。
https://forums.expo.io/t/push-notification-dynamic-icon/1684/5
小さいアイコンはどちらもExpoのデフォルトのもの(上向きの矢印のような)が表示されていますね。

AndroidのNotificationアイコンをカスタマイズする

小さいアイコンに関してはapp.jsonでの設定が必要なようです。
ドキュメントを参考にしてみます。

assets/notificationicon.png
notificationicon.png
説明通り、96px x 96pxで透過PNGの画像を作成しました。このページでは白く見える部分が透過されています。
これをapp.jsonで指定します。

app.json
    "notification": {
      "icon": "./assets/notificationicon.png",
      "color": "#00c2ad"
    },

アプリをビルドし、Notificationを送ってみると...
Android
小さいアイコンが設定したものに変わりました。もっとシンプルなアイコンにした方が良さそうです。
説明だとcolorで指定した色がオーバーレイされるはずなのですが、色は他のUIと共通のものになっていますね。これは端末のソフトウェアによるのかもしれません。
ひとまずExpoのアイコンではなくなったので良しとしましょう。

追記

端末によっては、ここで指定したアイコンがそのままの色で表示されることがあるようです。
Screenshot_2019-11-19-06-34-10.png
白い透過画像に差し替えると
Screenshot_2019-11-19-06-46-16.png
こんな感じに馴染みました。白色・透過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
指定した数字がつきました。
これはiOSのみで、Androidでは実現できず、単純に溜まっている通知の数がバッジとして表示されるのみです。
ネイティブの仕様ではAndroidもできるはずですが、今のところExpoでは対応していないということかと思います。

iOSではアプリ側でもバッジの制御ができます。
数字を0に戻す処理を足してみます。

import { Platform } from 'react-native';

if (Platform.OS === 'ios') {
  Notifications.setBadgeNumberAsync(0);
}

PNGイメージ.png

バッジが消えました。
ユーザーが通知に関わるコンテンツを閲覧したらバッジの数を変更するといったことがこれで可能です。(現在のバッジの数はNotifications.getBadgeNumberAsync()で取得できます。)

アプリ側で通知に反応する

通知が届いたときにアプリ側で処理するために、リスナーを登録してみます。

import { Notifications } from "expo";

Notifications.addListener(notification => {
  console.log('received notification', notification);
});

リスナーに渡されたNotificationの内容をコンソールに出してみると、こんな感じです。
React_Native_Debugger.png
remoteはPushNotificationかLocalNotificationかどうかが、
actionIduserTextはあとで紹介する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では、
PNGイメージ.png
アプリの上に重なります。この状態で通知をタップしたらreceivedに。

通知が上に引っ込んでから、通知センターを引き出して、通知をタップしたらselectedに。
PNGイメージ 2.png
あるいは
PNGイメージ 3.png
アプリに重なっている通知を深くタップしたり下にスワイプしてフォーカスしてからタップすると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では通知を深くタップするとこのようにボタンが表示されました。
PNGイメージ 4.png

一つ目のfooはテキストを入力させるように定義しているので、タップすると入力UIが表示されます。
PNGイメージ 5.png
これで送信ボタンをタップしたらアプリ側のリスナーが呼ばれます。
入力された内容は先ほどのuserTextから参照できます。

Androidでは...
Android
通知センターにそのまま表示されました。
テキスト入力もできます。送信ボタンのタイトル(submitButtonTitle)は効かず、アイコンになっていますね。
Android

あまり使わないでしょうが、カテゴリーを削除するためのメソッドも用意されています。

Notifications.deleteCategoryAsync(categoryId)

終わり

以上、ExpoのNotificationについて、使いそうな機能を検証してみました。
引き続きほかの機能も調べていこうと思います。

追記

各機能をExpoClient/スタンドアロンビルド/ストアからインストールのそれぞれで検証してみた状況をスプレッドシートにまとめています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした