みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Expoアプリのプッシュ通知用デバイストークンをCloud Firestoreに登録する 🔥

f:id:kimizuka:20200615114604p:plain

関連記事

Expoでつくったアプリにローカルサーバからプッシュ通知を送る 📛

blog.kimizuka.org

Expoでつくったアプリにサーバからプッシュ通知を送る 📛

blog.kimizuka.org

これまで、ローカルサーバからプッシュ通知送信、サーバからプッシュ通知送信を試しましたが、これまではデバイストークンを直書きしていました。
今回は、デバイストークンをCloud Firestoreに登録する部分をつくろうと思います。


注意事項

Expo(44.0.0)で開発した場合、 Firebase(9.6.8)を同時に使うと、「Can't find variable:IDBindex」というエラーが出るので、Firebase(9.6.7)を使って開発しました。

blog.kimizuka.org


ソースコード

import Constants from 'expo-constants';
import * as Notifications from 'expo-notifications';
import { initializeApp } from 'firebase/app';
import { doc, getFirestore, setDoc } from 'firebase/firestore';
import { useState, useEffect, useRef } from 'react';
import { AppState, AppStateStatus, View } from 'react-native';

const firebaseConfig = {
  apiKey: 'XXXXXXXX',
  authDomain: 'XXXXXXXX.firebaseapp.com',
  projectId: 'XXXXXXXX',
  storageBucket: 'XXXXXXXX.appspot.com',
  messagingSenderId: 'XXXXXXXX',
  appId: 'XXXXXXXX'
};

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

const app = initializeApp(firebaseConfig);

export default function App() {
  const appState = useRef(AppState.currentState);
  const [ expoPushToken, setExpoPushToken ] = useState('');

  useEffect(() => {
    AppState.addEventListener('change', handleAppStateChange);

    registerForPushNotificationsAsync().then((token) => {
      token && setExpoPushToken(token);
    });

    return AppState.removeEventListener('change', handleAppStateChange);
  }, []);

  useEffect(() => {
    if (expoPushToken) {
      (async () => {
        const db = getFirestore(app);
        const ref = doc(db, 'tokens', expoPushToken);

        await setDoc(ref, {
          token: expoPushToken
        });
      })();
    }
  }, [expoPushToken]);

  function handleAppStateChange(nextAppState: AppStateStatus) {
    if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
      registerForPushNotificationsAsync().then((token) => {
        if (token) {
          setExpoPushToken(token);
        }
      });
    }

    appState.current = nextAppState;
  }

  async function registerForPushNotificationsAsync() {
    let token;

    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') {
        return;
      }

      try {
        token = (await Notifications.getExpoPushTokenAsync()).data;
      } catch (err) {
        console.error(err);
      }
    }

    return token;
  }

  return (
    <View />;
  );
}

ざっくり、こんな感じで実装しました。


ポイント

useEffect(() => {
  if (expoPushToken) {
    (async () => {
      const db = getFirestore(app);
      const ref = doc(db, 'tokens', expoPushToken);

      await setDoc(ref, {
        token: expoPushToken
      });
    })();
  }
}, [expoPushToken]);


ここの部分でCloud Firestoreにデバイストークンを保存しています。

const ref = doc(db, 'tokens', expoPushToken);

これで、コレクションID「tokens」に対してデバイストークンをドキュメントIDにしたドキュメントを作成し、

await setDoc(ref, {
  token: expoPushToken
});

「token」フィールドにデバイストークンを書き込んでいます。
ドキュメントIDにデバイストークンをつかい、デバイストークンの重複を防いでいるのですが、これがベストプラクティスなのかはわかりません。