4
0

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 1 year has passed since last update.

鈴鹿高専Advent Calendar 2021

Day 17

firebase with react-native

Last updated at Posted at 2021-12-16

はじめに

こちらは鈴鹿高専 Advent Calendar 2021の17日目の記事です。

先日のpck2021モバイル部門に向けたアプリ開発でfirebaseを使ってみました!
とは言っても実際に使ったのはrealtime databaseとfirebase cloud messaging 、firebase authentication(のメールログイン)だけなのですが...

それぞれで使い方が調べてもなかなか分からないところがたくさんあったので備忘録としてまとめてみます
android限定なのでiosアプリの開発には役立たないかも

開発環境やモジュールのバージョン

バックエンドは10月中旬に開発をしたためnode.jsバージョンがフロントエンドよりも新しくなっています

言語:typescript

フロントエンド

react:17.0.1

react native:0.64.1

node.js:14.16.0

yarn:1.22.10

react-native-firebase : 12.7.3

バックエンド

Azure

OS:UbuntuServer 20.04.3 LTS

node.js:16.11.1

yarn:1.22.17

firebase-admin:10.0.0

firebaseとreact-native appの接続について

まずは接続について、いろんな記事を読んでみて難しそうだなぁと思いましたが実際にやってみると手探りでも2時間ほどで終わったので簡単でした

では順番に...

yarn 又は npm でモジュールをインストールして

yarn add @react-native-firebase/app
npm install @react-native-firebase/app

androidパッケージネームを変更していきます、パッケージネームは自由に決めれますが既存のものと被っているとまずいので調べましょう

  • market://details?id=<パッケージ名>
  • https://play.google.com/store/apps/details?id=<パッケージ名>

このふたつで調べて被ってなかったら良いみたいです、このパッケージ名は後でfirebaseコンソールで使うので覚えておきましょう

プロジェクトのファイルで変更する場所は

  • android/app/BUCK
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/com/[ProjectName]/MainActivity.java
  • android/app/src/main/java/com/[ProjectName]/MainApplication.java

packageのところと

  • android/app/build.gradle

applicationIdのところです
パッケージ名を変更したらandroid/app/src/main/java/com/[App Name]/MainActivity.javaandroid/app/src/main/java/[Package Name Folder]/MainActivity.javaに移動させます

次はfirebaseコンソールでのアプリの追加です
まずはコンソールにアクセスしてプロジェクトを作ってください
firebaseコンソールはここ

次にアプリを作成します

image.png

ここのandroidのボタンを押しすと3つ入力欄が出ますが、一番上のパッケージネーム以外はオプションなので入力する必要はありません
ただしauthenticationで電話番号認証や、firebase Dynamic Linksを使いたい場合は一番下のSHA-1を入力する必要があるのでキーを入力しましょう
android keystore sha-1でググると取得方法が分かります
一番上のパッケージネームに先ほど決めたものを入力してregistar appを押します
するとgoogle-services.jsonがダウンロードできるようになるので、ダウンロードしてandroid/appにコピーし、Nextを押します

次に、Project-level build.gradle の追加内容をandroid/build.gradleファイルに、App-level build.gradleandroid/app/build.gradleに追記します
これでfirebaseとの接続は完了です!

※バックエンドとの接続
firebaseはバックエンドからも使うことができます、今回はnode.jsを使ったのでその場合の接続方法を説明します
まずは必要なモジュールをインストールします

yarn add firebase-admin
npm install firebase-admin

次にfirebaseコンソールからプロジェクトの設定画面を開き、サービスアカウントのFirebase Admin SDKにある新しい秘密鍵の生成を押し、ファイルをダウンロード、またこのページに書いてあるコードをコピペしてスクリプトに貼り付けます、ダウンロードしたファイルはスクリプトが読める場所に置きましょう

realtime databaseについて

realtime databaseを使う前に必要なモジュールをインストールします

yarn add @react-native-firebase/database
npm install @react-native-firebase/database

次にこれをスクリプトファイルにインポートします

import database from "@react-native-firebase/database"

データベースへのアクセス

まずはrealtime databaseの構造について軽く説明します
簡単に言うと大きいjsonです

"root":{
  "child":{
    "string":"hogehoge",
    "number":1234
  },
}

このような感じになります
では実際にアクセスしてみましょう

import database from "@react-native-firebase/database"

database().ref("child").child("grandchild")

.ref("hogehoge")に親のから見た第一階層を指定します、空の場合は親の階層が指定されます
.child("hugahuga")には.ref()で指定した階層から見た子階層を指定します、.child()メソッドは続けて書くことで深い層まで指定できます

データ保存

データ保存は.push()もしくは.set()を使います
.push()を使うと一意のkeyが生成されますが使う機会があまり無かったので今回のpckでは.set()だけ使いました
指定したパスにデータがある場合は上書きされ、ない場合は追加されます
引数には{"key":value}のような感じで指定します、複数指定する場合は"key":valueの組み合わせを,で区切って追加すればできます
valueには文字列や数字のほか、bool型や配列も入れることができます

import database from "@react-native-firebase/database"

const Array : string[] = ["hogehoge", "hugahuga"]
const flag : boolean = false
const num : number = 0

database().ref().child("child").set({
  array : Array,
  bool : flag,
  number : num,
})

データ取得

データ取得はその時点でのデータを取得するときは.once("EventType", (snapshot)=>{})を使います
データベースの変更を検知してコールバック関数を実行したい場合は.on("EventType", (snapshot)=>{})を使います
引数には"value","child_changed","child_added","child_moved","child_removed"を取ります、"value"は単に値の取得をするのみで.once()と組み合わせて使うことが多いです
その他4つのEventTypeは.on()と組み合わせて使うことが多いです、"child_changed"は子要素が変更されたとき、"child_added"は子要素に追加されたとき、などイベントが発生するタイミングは各EventTypeの文字が意味するものです

コールバック関数に渡される引数には取得したデータが入っています

import database from "@react-native-firebase/database"

database().ref("hoeghoge").child("child").once("value", (snapshot) => {
  snapshot.child("hugahuga").val() //valueを取得:any
  snapshot.key //keyを取得:string このコードの場合は"child"
})

このようにすることで子要素の値やkeyを取り出すことが出来ます
また中に入っている要素の数が不明で、全部を取り出したいときは

import database from "@react-native-firebase/database"

const values : any[] = [];

database().ref().once("value", (snapshot)=>{
  snapshot.forEach((element) => {
    if(element){
      values.push(element.val())
    }
    else{
      return true
    }
  })
})

このように書くことで値を取り出すことが可能です、forEachの中では取り出した子要素がnullやundefinedの場合は処理が実行できないのでifで判定してelseの場合にreturn trueをすることにより処理を停止させる必要があります
.onはリスナーのようなものなので必要に応じて破棄する必要があります(そうでないとメモリ不足でアプリが落ちたり...)

import database from "@react-native-firebase/database"
import {useEffect} from "react"

useEffect(() => {
  const listner = database().ref().on("child_changed", (snapshot)=>{})
  return (() => {database().ref().off("child_changed", listner)})
},[])

このようにreact-nativeのuseEffect内で使う場合は.off()を使いreturnのところでリスナーを破棄します
useEffectの第2引数には特別何かなければ[]を渡しましょう、指定しないのはダメ、絶対

データの削除

データの削除には.remove()メソッドが用意されていますが動作しませんでした、そのため.set()を用いて削除しました

import database from "@react-native-firebase/database"

database().ref().child("child").set(null)

オフラインでもデータベースにアクセスしたい場合

データベースの永続化を有効にすることでオフラインでも使えるようになります

index.ts
import database from '@react-native-firebase/database'

database().setPersistenceEnabled(true)

オフライン時に変更したところはオンライン復帰時に自動的にmergeされます

バックエンド側からのアクセス

バックエンドからデータベースにアクセスする際は

import admin from "firebase-admin"

const database = admin.database

database().ref()

のように書きます、あとはフロントエンドと同じような感じで保存や取得ができます

注意すること!

realtime database関連の処理は全て非同期処理です、そのためレンダリングや受け取ったデータをもとに更にデータを保存したりするときはasync await.then()を使って適切タイミングで処理を発火させる必要がります(私はここで何度も躓きました)
レンダリングでrealtime databaseから取得したデータを使う場合、reactのstateを使うとうまくできるかもしれません
これでrealtime databaseについては終わりです

firebase cloud messagingについて

firebase cloud messagingはバックエンドからフロントエンドにデータを渡したり通知を送信するときに使います、使い方によっては処理を発火させることもできます

バックエンドからデータや通知を送信する方法について

import admin from "firebase-admin"

admin.initializeApp()//適宜必要な引数を渡してください

admin.send({
  notification : {
    title : "タイトル",
    body : "内容",
  },//通知
  data : {
    key : valule,
  },//データ
  topic : "topic", token : "token",//宛先、どちらかを指定
})//送信

通知を送信したい場合はnotificationを、データを送信したい場合はdataを使います、両方送信したい場合はこのコードのように両方指定しましょう
送信先の指定はtopicまたはtokenを指定します
topicはバックエンド側で自由に決めることができ、フロントエンド側で登録することで受信できます
tokenはフロントエンド側でtokenを取得し、それを使ってデータなどを送信します、tokenはrealtime databaseを通してフロントエンドからバックエンドに渡すことが一般的みたいです(今回のプロジェクトではtokenを使ってないので説明は割愛します)

フロントエンドでデータなどを受け取ったときの処理について

まずはモジュールをインストールします

yarn add @react-native-firebase/messaging
npm install @react-native-firebase/messaging

notificationにきたものはアプリの状態がフォアグラウンド以外であれば初期の状態で通知が表示されます、フォアグラウンドであれば通知は表示されません

通知をタップして起動したときの動作はバックグラウンドで起動してる場合と起動してない場合で定義の仕方が少し異なります
起動している場合は

import messaging from "@react-native-firebase/messaging"
import {useEffect} from "react"

useEffect(() => {
  const unsubscribe = messaging().onNotificationOpenedApp((message) => {/*ここに実行したい処理を書く*/})
  return unsubscribe
},[])

起動していない場合は

import messaging from "@react-native-firebase/messaging"
import {useEffect} from "react"

useEffect(() => {
  messaging().getInitialNotification().then((message) => {/*ここに実行したい処理を書く*/})
},[])

このように書きます

受け取ったデータは.onNotificationOpenedAppもしくは.getInitialNotification().thenで取得した変数に入っています
データの型はRemoteMessageで詳しくはリファレンスに書いてあります

messaging().getInitialNotification().then((message) => {
  const data = message.data//{ [key: string]: string }
  const notification = message.notification//リファレンスを参照 https://rnfirebase.io/reference/messaging/notification
})

dataの方はkeyを使い値を取り出します
notificationの方はプロパティが多いためリファレンスを参照してください(説明するのがめんどくさい)

tokenの取得について

import messaging from "@react-native-firebase/messaging"

messaging().getToken().then((token) => {})

このように取得します、返ってくる変数の型はstringです
またtokenが更新された場合に処理を実行したい場合は

import messaging from "@react-native-firebase/messaging"

messaging().onTokenRefresh((token) => {/*実行したい処理*/})

このように書きます、このメソッドはリスナーなのでuseEffectなどに書く場合はreturnをしてあげたほうがいいと思います(使ってないから分からないけど)
ここで取得したtokenをバックエンドに渡せればtokenを使ってデータなどを送信できます

フロントエンドでtopicを追加したり削除したりする方法について

import messaging from "@react-native-firebase"

messaging().subscribeToTopic(topic : string)//topic追加
messaging().unsubscribeFromTopic(topic : string)//topic削除

これでfirebase cloud messagingについては終わりです

firebase Authentication について

続いてはfirebase Authenticationについてです、これを使うことで電話番号認証やメール認証、更にgoogleログインや各種SNSアカウントでのログインを実装することが出来ます
今回はメールにdynamic linksを送信する方法での認証を実装したのでその説明をします
まずは認証周りのモジュールをインストールします

yarn add @react-native-firebase/auth
npm install @react-native-firebase/auth

yarn add @react-native-firebase/dynamic-links
npm install @react-native-firebase/dynamic-links

次にBundleIdをとる必要があるため次のモジュールもインストールしましょう

yarn add react-native-device-info
npm instal react-native-device-info

またメール送信で入力したメールアドレスは認証でも使うのでAsyncStorageなどに保存しておくと便利です

yarn add @react-native-community/async-storage
npm install @react-native-community/async-storage

認証にはfirebase Dynamic Linksを使うので予め設定をしておきましょう(このサイトが参考になりました)

認証の実装

では実際にメールアドレスを入力し認証をする機能を実装していきます

import auth from "@react-native-firebase/auth"
import DeviceInfo from "react-native-device-info"
import AsyncStorage from "@react-native-community/async-storage"

//認証メールの設定

const actionCodeSettings = {
  url : 'http://example.com'/*ディープリンク(対応端末以外でリンクが押されたときに開かれる)を指定*/,
  handleCodeInApp: true,
  iOS: {
    bundleId: DeviceInfo.getBundleId(),
  },
  android: {
    packageName: DeviceInfo.getBundleId(),
    installApp: true,
  },
  dynamicLinkDomain : 'example.page.link',
}

mailaddress : string = ""//メールアドレスを代入

auth().sendSignInLinkToEmail(mailaddress, actionCodeSettings)
  .then(() => {
    AsyncStorage.setItem('key', mailaddress)
  })//メール送信とメールアドレスの保存

これでメールが送信されます
次にメールリンクを開いた際の処理について
アプリがバックグラウンドで開かれているかそうでないかでメソッドが変わります

import auth from "@ract-native-firebase/auth"
import {useEffect} from "react-native"
import dynamiclinks, {
  FirebaseDynamicLinksTypes,
} from '@react-native-firebase/dynamic-links'

//バックグラウンドで起動していないとき
dynamiclinks().getInitialLink().then((link) => {
  if(link){
    _handleForgroundDynamicLinks(link)
  }
})

//バックグラウンドで起動しているとき
useEffect(() => {
  const unsubscribe = dynamiclinks().onLink(_handleForgroundDynamicLinks)
  return () => unsubscribe
},[])

//リンクのハンドリング
const _handleForgroundDynamicLinks = (llink : FirebaseDynamicLinksTypes) => {
  AsyncStorage.getItem('key')
    .then((mailaddress) => {
      if(mailaddress){
        auth().signInWithEmailLink(mailaddress, link.url)//認証
          .then(() => {/*ログイン後にしたい処理*/})
      }
    })
}

バックグラウンドで起動しているときは.onLink()メソッドでリスナーを作ります
本当は電話番号認証を実装する予定だったんですがreCAPTCHA認証のところがめんどくさそうだったので、PCKのアプリではメールリンク認証を採用しました(Expoを使っていたら簡単に実装できたのかも?)

まとめとか感想

firebaseを使うとバックエンド・フロントエンド間の通信やデータベースの構築、認証が簡潔なコードで実装できました
しかし使っている人が多いのに「これだ!」という記事が少なく、実装に困ることがかなりありました
この記事ではそのあたりを重点的に説明したので今後firebaseを使って開発をする人の参考になればと思います

数日かけて書いたので書き方が統一されてないです。。。

次はfirebaseとreact、electronを使ってデスクトップアプリを開発したいなぁ

4
0
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
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?