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
はこうした機能もカバーしています。
まずファイルを以下のように書き換えます。
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秒後にプッシュ通知を配信するボタンを追加するために、サンプルコードを以下のように書き換えてみます。
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',
},
});
プッシュ通知をカスタマイズする
プッシュ通知の内容をカスタマイズしたい場合は、 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();
イベントリスナーには引数として Notification
3 イベントが渡されます。
Notification
イベントからはプッシュ通知のコンテンツやID等の情報を取得できます。
また、addNotificationReceivedListener
メソッドは戻り値として Subscription4 オブジェクトを返します。
このオブジェクトの remove
メソッドを使えば、今回登録されたイベントリスナーを解除できます。
アプリがバックグラウンド / 停止中のとき
アプリがバックグラウンドの時に、プッシュ通知の受信をトリガーにコードを自動実行する方法はあるでしょうか?
残念ながらExpoではそうした機能はサポートしていません。
代わりに、 addNotificationResponseReceivedListener
メソッドを使って、プッシュ通知をユーザーがタップしたときに発火させるイベントリスナーを登録できます。
const subscription = Notifications.addNotificationResponseReceivedListener(e => {
console.log(e.notification.request.content.body);
})
subscription.remove();
イベントリスナーには引数として NotificationResponse
5 イベントが渡されます。
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 というモジュールを使えば、アプリケーションのライフサイクル中で変らないシステム情報(例: シミュレーターか実機かどうか)を取得できます。
本番用のアプリケーションを開発する上でこうしたモジュールは便利だと思いますので、本稿の結びとして紹介します。