こんにちは、株式会社カミナシのエンジニア @imuです。
はじめに
と、いうことでPush通知の話です!
カミナシではReact Native(Expo)でアプリケーションを開発しています。
Push通知の実装って意外と難しいイメージありませんか? アプリケーション側での監視、通知内容を保存しておくサーバー、どういうタイミングで通知するかのパターン等を考えると、簡単なパッケージがあれば楽なのに…と思ってしまいます。
Expoには便利なPackageがいくつかあって、通知のPackageもあります! どれくらい簡単に実装できるか、ご紹介出来ればと思います。
『カミナシ』のアプリケーションにはまだ実装されていないのですが、これくらい簡単であれば入れたいな…。
環境構築
ExpoCLIのインストール
npm install -g expo-cli
インストールが終わったらひな形を作成しましょう。 今回はManaged workflowのblank(TypeScript)を利用します。
expo init notification
通知のパッケージをインストール
公式ドキュメントに従って通知のパッケージをインストールします。 BareWorkflowを使った場合はこちらを参照してください。
expo install expo-notifications
Expoのサーバーを利用してPush通知が可能になります。 自前でサーバーを用意することがないので非常に楽ちんです。
早速使ってみよう
大まかな流れは以下の通りです
- 通知の権限、Pushトークンの取得
- 通知したい内容を登録する
- アプリケーション側で通知内容を受け取る
通知の権限、Pushトークンの取得
if (Constants.isDevice) { // 通知の権限を確認します const { status: existingStatus } = await Notifications.getPermissionsAsync() let finalStatus = existingStatus if (existingStatus !== 'granted') { // 通知の権限がない場合は、再度通知の権限を確認します const { status } = await Notifications.requestPermissionsAsync() finalStatus = status } if (finalStatus !== 'granted') { alert('Failed to get push token for push notification!') } else { // Pushトークンの取得 const token = (await Notifications.getExpoPushTokenAsync()).data } } else { // Push通知はSimulatorでは確認できない alert('Must use physical device for Push Notifications') }
通知したい内容を登録する
// 通知したい内容をRequestBodyに設定する const pushMessage = { // Push通知のトークン to: expoPushToken, // Push通知の音を設定 sound: 'default', // Push通知のタイトル title: title, // Push通知の内容 body: body, // dataは実際にPush通知には表示されないがアプリケーション側で参照することが出来るので、アプリケーション側になにかさせる場合は設定すると良さそう data: { addData: data, sample: 'test' } } await fetch('https://exp.host/--/api/v2/push/send', { method: 'POST', headers: { Accept: 'application/json', 'Accept-encoding': 'gzip, deflate', 'Content-Type': 'application/json' }, body: JSON.stringify(pushMessage) })
fetch関数を呼び出しているだけなので、コマンドラインからcurl
を実行しても良いです。
% curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" \ (git)-[main] > -d '{"to":"ExponentPushToken[Pushしたい先のトークン]","title":"kaminashi","body":"blog"}' {"data":{"status":"ok","id":"hoge"}}%
他にもスケジュール通知もあるので詳しくはこちらを確認してみてください。
アプリケーション側で通知内容を受け取る
// アプリがフォアグラウンドの状態で通知を受信したときに起動 notificationListener.current = Notifications.addNotificationReceivedListener(notification => { // notificationには通知内容が含まれています setNotification(notification) }) // ユーザーが通知をタップまたは操作したときに発生します // (アプリがフォアグラウンド、バックグラウンド、またはキルされたときに動作します) responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { console.log(response) })
最終的なコード
公式サイトのサンプルを元に、画面から入力した内容でPush通知できるように変更したものがこちらです。
import Constants from 'expo-constants' import * as Notifications from 'expo-notifications' import React, { useState, useEffect, useRef } from 'react' import { Text, View, Button, Platform, TextInput, StyleSheet, TouchableHighlight } from 'react-native' import { Subscription } from '@unimodules/core' export default function App() { const [expoPushToken, setExpoPushToken] = useState('') const [notification, setNotification] = useState<Notifications.Notification>() const notificationListener = useRef<Subscription>() const responseListener = useRef<Subscription>() const [title, onChangeTitle] = useState<string>('') const [body, onChangeBody] = useState<string>('') const [data, onChangeData] = useState<string>('') useEffect(() => { // 通知を受信した時の振る舞いを設定 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: false, shouldSetBadge: false }) }) // Expo Pushトークンを取得 registerForPushNotificationsAsync().then(token => token && setExpoPushToken(token)) // アプリがフォアグラウンドの状態で通知を受信したときに起動 notificationListener.current = Notifications.addNotificationReceivedListener(notification => { setNotification(notification) }) // ユーザーが通知をタップまたは操作したときに発生します // (アプリがフォアグラウンド、バックグラウンド、またはキルされたときに動作します) responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { alert('ユーザーが通知をタップしました') console.log(response) }) // アンマウント時にリスナーを削除 return () => { const notification = notificationListener.current notification && Notifications.removeNotificationSubscription(notification) const response = responseListener.current response && Notifications.removeNotificationSubscription(response) } }, []) return ( <View style={styles.container}> <Text style={styles.title}>プッシュ通知を試してみよう🙌</Text> <View style={styles.pushView}> <Text style={styles.title}>プッシュする内容を入力📤</Text> <TextInput style={styles.input} placeholder='タイトルを入力してください' value={title} onChangeText={onChangeTitle} /> <TextInput style={styles.input} placeholder='メッセージを入力してください' value={body} onChangeText={onChangeBody} /> <TextInput style={styles.input} placeholder='追加メッセージ(通知には表示されない)' value={data} onChangeText={onChangeData} /> </View> <View style={styles.pushView}> <Text style={styles.title}>プッシュした内容を表示📥</Text> <View style={styles.output}> <Text style={styles.outputText}>タイトル: {notification && notification.request.content.title}</Text> <Text style={styles.outputText}>メッセージ: {notification && notification.request.content.body}</Text> <Text style={styles.outputText}> 追加メッセージ: {notification && JSON.stringify(notification.request.content.data)} </Text> </View> </View> <View style={styles.buttonView}> <TouchableHighlight style={styles.button}> <Button title='プッシュ通知' color='white' onPress={async () => { await sendPushNotification(expoPushToken, title, body, data) }} /> </TouchableHighlight> <TouchableHighlight style={styles.button}> <Button title='スケジュールのプッシュ通知' color='white' onPress={async () => { await schedulePushNotification() }} /> </TouchableHighlight> </View> </View> ) } async function sendPushNotification(expoPushToken: string, title: string, body: string, data: string) { const pushMessage = { to: expoPushToken, sound: 'default', title: title, body: body, data: { addData: data, sample: 'test' } } await fetch('https://exp.host/--/api/v2/push/send', { method: 'POST', headers: { Accept: 'application/json', 'Accept-encoding': 'gzip, deflate', 'Content-Type': 'application/json' }, body: JSON.stringify(pushMessage) }) } async function schedulePushNotification() { await Notifications.scheduleNotificationAsync({ content: { title: 'スケジュール通知 📬', body: 'スケジュール通知されました!' }, trigger: { seconds: 2 } }) } async function registerForPushNotificationsAsync() { let token: string = '' if (Constants.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync() let finalStatus = existingStatus if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync() finalStatus = status } if (finalStatus !== 'granted') { alert('Failed to get push token for push notification!') return } token = (await Notifications.getExpoPushTokenAsync()).data } else { alert('Must use physical device for Push Notifications') } if (Platform.OS === 'android') { Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C' }) } return token } export const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'space-around' }, pushView: { width: '100%', alignItems: 'center', justifyContent: 'center' }, title: { fontWeight: 'bold', fontSize: 34, lineHeight: 49 }, input: { width: '80%', height: 40, margin: 12, borderWidth: 1 }, output: { width: '100%', alignItems: 'center', justifyContent: 'center' }, outputText: { width: '80%', fontWeight: 'bold', fontSize: 18, lineHeight: 40, margin: 12, borderWidth: 1 }, buttonView: { width: '100%', alignItems: 'center', justifyContent: 'center' }, button: { height: 40, width: '50%', borderRadius: 12, backgroundColor: 'gray', marginBottom: 24 } })
iPadOS 14.4の実機で画面収録したものがこちらになります。 入力した内容でプッシュ通知を行い、その後スケジュール通知(ボタン押下から2秒後に通知される)を行っています。 gyazo.com
APNs(Apple Push Notification Service), FCM(Firebase Cloud Messaging)を利用した通知の紹介
APNs / FCM を理解していて、ExpoのPush通知では実現できない場合に検討するほうが良さそうです。
おわりに
expo-notificationsを使えば簡単にプッシュ通知が実装できました!Managed Workflowの場合は簡単に実装出来るので便利ですね。 次回は他のPackageで面白そうなものを紹介出来ればと思います。
最後に弊社ではエンジニアを募集しております。 興味がある、話を聞いてみたい、応募したいという方はお気軽にご応募ください!