45
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Expoでのプッシュ通知の仕方についてまとめ(ローカル通知編)

Posted at

Stack Overflow Developer Survey 2020によれば、世界で最も使われているプログラミング言語は8年連続でJavaScriptでした。
名実ともに、エンジニアにとって最も馴染み深い言語だと思います。
そして、このJavaScriptを使ってモバイルアプリケーション開発をできるようにしたフレームワークがReact Nativeです。
React Nativeを使えば、JavaScript(React)でiOS/Android双方のアプリ開発ができます。

React Nativeの開発支援ツールは色々とあると思うのですが、本稿では**Expo**を使います。
Expoはオープンソースのツールチェインで、クラウドビルド、ローカルでの実機テスト、カメラや位置情報の利用、OTAアップデート等の様々な機能を簡単に実装することができます。
以降の項では、Expoを使ってローカル通知をする方法を解説します。

ローカル通知とは?

プッシュ通知イメージ図

プッシュ通知を使えば、アプリが起動中かに関わらず、そのアプリに関する情報をユーザーに通知することができます。
プッシュ通知にはローカル通知リモート通知の2種類があり、それぞれ実装方法等が異なります。

ローカル通知は、アプリから直接システムに対してプッシュ通知の配信を要求します。
システムは指定されたコンテンツや配信条件(日時や位置情報等)に基づいて、ユーザーへ通知を表示します。
オフラインでも利用することができ、配信に遅延が生じないため、リマインダーやカレンダーアプリ等で便利だと思います。

一方、リモート通知の場合はオンラインで利用します。
まず、ユーザーのデバイストークン1を取得し、自社で用意したアプリケーション・サーバーへ保存しておきます。
アプリケーション・サーバーはプッシュ通知を配信するタイミングで、デバイストークンと配信するコンテンツを併せて、AppleやGoogleが提供するプッシュ通知配信サーバーへ配信依頼を出します。
複数のユーザーに対して一括で通知することができるため、マーケティング関係のメッセージを配信したり、サービス提供元から何らかのアナウンスをする際に便利ですが、配信において通信が必要なため配信遅延等が生じる可能性があります。

ローカル通知もリモート通知も、ユーザーからすればどちらも同じように見えます。
Expoを使えばどちらも容易に実装することができますが、前述の通り、本稿ではローカル通知の実装方法について記載します。

セットアップ

Expoを使えば、まるでRuby on Railsのようにコマンド一つでアプリケーションの雛形を作ることができます。
そのためにいくつか必要なツールがありますので、それらも踏まえたセットアップ手順を記載します。
※ なお本稿では開発環境として macOS Catalina 10.15.6 を使用しています。また本稿執筆時点(2020/08/10)での手順になりますので、最新の情報はこちらからご確認ください。

1. Expo Client をスマートフォンにインストールする。

Expo Client は開発中のモバイルアプリケーションの実機テストを行うためのアプリです。
iOS版Android版の両方がありますので、お手持ちの端末に合わせてインストールしてください。

2. Node.js をダウンロードする。

Node.js はJSエコシステムを使うために必要になります。最新のLTS版の使用を推奨します。
※ 本稿では v12.18.3 を使用しています。

3. expo-cli をインストールする。

expo-cli はプロジェクトの新規作成やサーバーの起動、ビルドなどをExpoで行うためのコマンドラインツールです。
以下のコマンドでインストールできます。

$ npm install expo-cli --global

※ なお、本稿でのバージョンは 3.23.3 です。

4. プロジェクトを立ち上げる
$ expo init localNotificationTest --npm -t blank
$ cd localNotificationTest 
5. expo-notifications をインストールする

expo-notifications はExpoでプッシュ通知を行うためのモジュールです。
以下のコマンドでインストールできます。

$ expo install expo-notifications
6. サーバーを立ち上げる。
$ npm start
7. QRコードをスキャンする。

iOSの場合はカメラで、Androidの場合はExpo Clinentでスキャンすると、コードが読み込まれます。
Open up App.js to start working on your app! というテキストが表示されたら成功です。

ユーザーに通知権限を要求する

さて、プッシュ通知をデバイスに表示するためには、事前にユーザーから許可(通知権限)を得る必要があります。
expo-notifications はこうした機能もカバーしています。
まずファイルを以下のように書き換えます。

localNotificationTest/App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Notifications from 'expo-notifications';

export default function App() {
  React.useEffect(() => {
    requestPermissionsAsync();
  })

  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

const requestPermissionsAsync = async () => {
  const { granted } = await Notifications.getPermissionsAsync();
  if (granted) { return }

  await Notifications.requestPermissionsAsync();
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
  },
});

React NativeはFast Refresh機能がありますので、ファイルを保存すると自動的に変更がExpo Clinentにも反映されると思います。
もしもうまく変更が反映されなかった場合は、Expo Clinentの画面を三本指でタップし続けると表示されるメニュー画面から「Reload」を選択してください。
以下のようなポップアップが表示されれば成功です。
通知権限リクエストイメージ図

通知権限に関するAPIについて少し解説します。
expo-notifications は、現時点の通知権限の情報を取得する getPermissionsAsync と、ユーザーに通知権限を要求するポップアップを出す requestPermissionsAsyncという2つのメソッドを用意しています。
どちらも非同期関数で実装されており、戻り値としてPromiseを返します。
これ以外にも 〜Async という命名の関数はすべて非同期関数になります。
呼び出す際は、async関数 / await演算子 と組み合わせて使うと便利だと思います。

また、requestPermissionsAsyncメソッドについて、一点だけ注意点があります。
iOS でも Androidでも、あるアプリから繰り返し同じ権限リクエストをすることは禁止されています。
したがって、アプリで過去に一度でも requestPermissionsAsync を使った場合は、それ以降は通知権限リクエストのポップアップを表示することはできません。

しつこくポップアップを出すとユーザーに不快感を与えてしまうため、この制約はプロダクション環境では問題ないのですが、開発環境でExpo Clinentを使って通知権限周りのテスト(例: ポップアップでDon't Allowを選択した場合にあるべき振る舞いになるか?)をする場合は問題になることがあります。
Expo Clinentをインストールし直さなければ、権限リクエストの再テストができないためです。

iOSシミュレーター(Android Studioエミュレーター)を使えばこの問題はある程度緩和することができます。
一旦シミュレーター内でExpo Clinentを削除し、再び$ npm startコマンドでサーバーを立ち上げ直せば、自動的にExpo Clinentが再インストールされるためです。
iOS ミュレーターをセットアップする場合はこちらを、Android Studio エミュレーターをセットアップする場合はこちらの記事をご参考ください。

アプリがバックグラウンドで起動/クローズしているときにプッシュ通知する

プッシュ通知を実際に送信する場合は、scheduleNotificationAsync というメソッドを使います。
例えば60秒後にプッシュ通知したい場合は、以下のように書きます。

import * as Notifications from 'expo-notifications';

Notifications.scheduleNotificationAsync({
  content: {
    body: 'テスト',
  },
  trigger: {
    seconds: 60,
  },
});

引数に渡したオブジェクトの content プロパティが配信内容を表し、 trigger プロパティが配信時刻などの配信条件を表します。
いずれも必須のプロパティです。

タップすると3秒後にプッシュ通知を配信するボタンを追加するために、サンプルコードを以下のように書き換えてみます。

localNotificationTest/App.js
import React from 'react';
import { StyleSheet, View, Button } from 'react-native';
import * as Notifications from 'expo-notifications';

export default function App() {
  React.useEffect(() => {
    requestPermissionsAsync();
  })

  return (
    <View style={styles.container}>
      <Button
        title='3秒後にプッシュ通知する'
        onPress={scheduleNotificationAsync}
      />
    </View>
  );
}

const scheduleNotificationAsync = async () => {
  await Notifications.scheduleNotificationAsync({
    content: {
      body: 'test'
    },
    trigger: {
      seconds: 3,
    }
  })
}

const requestPermissionsAsync = async () => {
  const { granted } = await Notifications.getPermissionsAsync();
  if (granted) { return }

  await Notifications.requestPermissionsAsync();
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
  },
});

以下のような挙動になったらうまく行っていると思います。
push_notification_after_3s.gif

プッシュ通知をカスタマイズする

プッシュ通知の内容をカスタマイズしたい場合は、 content プロパティにわたすオブジェクトを変更します。
例えば上述のプッシュ通知にタイトルをつける場合は以下のように書きます。

content: {
  body: 'test',
  title: 'title'
}

以下のようにタイトルが表示されます。
プッシュ通知のタイトルイメージ図

subtitle プロパティをつけると、タイトルと本文の間に副題をつけることができます。

content: {
  body: 'test',
  title: 'title',
  subtitle: 'subtitle'
}
プッシュ通知のサブタイトルイメージ図

content プロパティにわたすオブジェクトで使える代表的なプロパティは以下になります。

プロパティ名
body プッシュ通知の本文
title 太字で表示されるタイトル
subtitle iOSの場合、タイトルと本文の間に挿入される太字のサブタイトル
Androidの場合は端末ごとの仕様に依存する
sound プッシュ通知を受信したときの着信音の設定
値がnull, またはプロパティ自体が省略されている場合は着信音なしに
値が'default' または 'defaultCritical' または 'custom' の場合は着信音を鳴らす
badge 通知バッジ数を指定した数字にする(後述)
data ユーザーには表示されないが、プッシュ通知を介してアプリから取得できるデータ(オブジェクト)をセットする

その他のcontent プロパティで使える設定はこちらから確認できます。


配信時刻を変更する場合は trigger プロパティで指定します。
trigger プロパティは、渡す値のデータのタイプによって振る舞いが変わります。

例えば、現在時刻から3秒後にプッシュ通知する場合は以下のように書きます。

trigger: {
  seconds: 3,
}

60秒ごとに繰り返し通知する場合は以下になります。
※ 繰り返し通知する場合は、少なくとも1分以上の間隔をあける必要があります。

trigger: {
  repeats: true,
  seconds: 60,
}

配信時刻をUnix時刻2で指定する場合は以下になります。

trigger: Date.now() + (1000 * 3)

毎日15時00分に通知する場合は以下になります。

trigger: {
  hour: 15,
  minute: 00,
  repeats: true,
}

以上が基本的なスケジュール設定のサンプルになります。 もっと複雑なスケジューリングをしたい場合はどうでしょうか? 例えば毎月第一土曜日の15時00分に通知する場合は以下になります。
trigger: {
  weekday: 7,
  weekdayOrdinal: 1,
  hour: 15,
  minute: 00,
  repeats: true
}

ただし、この記法はiOSの場合にしか使えません
expo-notifications が内部的にUNCalendarNotificationTrigger というiOSのネイティブコードをラップしているためです。
利用できる条件はこちらをご参照ください。

アプリがフォアグラウンドのときに通知する

アプリが起動中のタイミングでプッシュ通知を受信した場合、そのプッシュ通知をどう扱うかはアプリ側に委ねられます。
Expoのデフォルトではプッシュ通知は破棄されます。

アプリが起動中であろうとユーザーに表示したい場合は setNotificationHandler というメソッドで設定します。

サンプルコードを次のように書き換えてください。

import React from 'react';
import { StyleSheet, View, Button } from 'react-native';
import * as Notifications from 'expo-notifications';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

// 以下同じです

以下のように、アプリが起動中でもプッシュ通知を表示できるようになります。
フォアグラウンドでの表示イメージ動画

プッシュ通知をキャンセルする

さて、プッシュ通知をキャンセルする方法について見ていきます。
特にリピート設定をした場合は手動でキャンセルしない限り永遠に通知されてしまいます。
expo-notifications では、プッシュ通知をキャンセルする方法を2つ用意しています。

すべてのプッシュ通知をキャンセルする

現在、スケジュールされているすべてのプッシュ通知をキャンセルする場合は以下のAPIを使います。

Notifications.cancelAllScheduledNotificationsAsync();

特定のプッシュ通知をキャンセルする

この場合、キャンセルしたいプッシュ通知のIDを取得する必要があります。
getAllScheduledNotificationsAsync メソッドを使うと、スケジュールされているすべてのプッシュ通知情報を配列で取得できます。

const notifications = await Notifications.getAllScheduledNotificationsAsync();
const identifier = notifications[0].identifier; 
console.log(identifier) // 例:  "a2772de7-e1ce-4a18-ab1d-8f4f3102dea7"

※ 取得できるデータについてはこちらからご確認ください。

IDを取得できたら、今度はそれを cancelScheduledNotificationAsync メソッドに渡します。

Notifications.cancelScheduledNotificationAsync(identifier);

以上の方法で、指定したプッシュ通知をキャンセルできます。

イベントリスナーを登録する

Expoではプッシュ通知に関するイベントリスナーを登録できます。
これにより、プッシュ通知関係のイベントが発生したときに自動的にコードを発火させることができます。

アプリがフォアグラウンドのとき

プッシュ通知を受信したタイミングでコードを発火させる場合は、 addNotificationReceivedListener メソッドを使います。

const subscription = Notifications.addNotificationReceivedListener(e => {
  console.log(e.request.content.body);
})
subscription.remove();

イベントリスナーには引数として Notification3 イベントが渡されます。
Notification イベントからはプッシュ通知のコンテンツやID等の情報を取得できます。

また、addNotificationReceivedListener メソッドは戻り値として Subscription4 オブジェクトを返します。
このオブジェクトの removeメソッドを使えば、今回登録されたイベントリスナーを解除できます。

アプリがバックグラウンド / 停止中のとき

アプリがバックグラウンドの時に、プッシュ通知の受信をトリガーにコードを自動実行する方法はあるでしょうか?
残念ながらExpoではそうした機能はサポートしていません。

代わりに、 addNotificationResponseReceivedListener メソッドを使って、プッシュ通知をユーザーがタップしたときに発火させるイベントリスナーを登録できます。

const subscription = Notifications.addNotificationResponseReceivedListener(e => {
  console.log(e.notification.request.content.body);
})
subscription.remove();

イベントリスナーには引数として NotificationResponse5 イベントが渡されます。
addNotificationReceivedListener メソッドと同様に、 remove メソッドで購読を解除できます。

addNotificationReceivedListener メソッドと addNotificationResponseReceivedListener メソッドでは、それぞれイベントのデータのタイプが異なる点にご注意ください。

通知バッジを操作する

アプリに通知バッジをつけたり、通知バッジ数を変更したい場合があります。
通知バッジとは、ホームスクリーンのアプリアイコンのそばに表示される数字つきのアイコンのことです。
通知バッジイメージ図

Expoではこの通知バッジを操作する方法を二通り用意しています。

手動で設定する

setBadgeCountAsync メソッドを使えば、アプリが起動中のときに通知バッジを操作できます。

Notifications.setBadgeCountAsync(5);

引数で指定した数字の通知バッジをアプリにセットします。
※ なお、引数を 0 にすると、通知バッジを取り消すことができます。

プッシュ通知のペイロードで指定する

プッシュ通知のコンテンツに badge プロパティを渡すと、指定した通知バッジ数に変更できます。

await Notifications.scheduleNotificationAsync({
  content: {
    body: 'test',
    badge: 1,
  },
  trigger: {
    seconds: 3
  }
})

プッシュ通知を受信したタイミングで自動的に通知バッジ数が変更されます。
プッシュ通知のペイロードで通知バッジ数指定する

※ なお、この方法でアプリが起動中のときに通知バッジを変更する場合は、 setNotificationHandler メソッドで設定が必要です。

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: true,
  }),
});

shouldSetBadge: true にすれば、プッシュ通知の受信をきっかけに通知バッジを操作できます。

アプリがバックグラウンドまたは停止中のときに、自動的に通知バッジ数をインクリメント(デクリメント)する方法はありません。
そのため、基本的にはユーザーの通知バッジ数はサーバーで管理しておく必要があると思います。

まとめ

expo-notifications を使えばプッシュ通知を簡単に実装できます。
またExpoには他にも様々なモジュールがあります。
例えば expo-permissions というモジュールを使えば、通知権限以外の権限リクエスト(例: 位置情報)を行うことができます。
expo-constants というモジュールを使えば、アプリケーションのライフサイクル中で変らないシステム情報(例: シミュレーターか実機かどうか)を取得できます。

本番用のアプリケーションを開発する上でこうしたモジュールは便利だと思いますので、本稿の結びとして紹介します。

参考リンク

expo-notifications公式ドキュメント

  1. デバイスごとの一意の識別キー。プッシュ通知における宛先のこと。

  2. 協定世界時 の1970年1月1日深夜0時からの経過ミリ秒数のこと。

  3. Notificationオブジェクト APIリファレンス

  4. Subscriptionオブジェクト APIリファレンス

  5. NotificationResponseオブジェクト APIリファレンス

45
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?