masalibの日記

システム開発、運用と猫の写真ブログです

React Firebase入門 Realtime Databaseでchatアプリ(書き込み)

Firebase Realtime Databaseを使ってChatのアプリを作りたいと思います

この記事はReact Firebase入門シリーズです 1-1-1・ Firebase初期設定とFirebaseAuthのSignUp
1-1-2・ 「react-hook-form」を入れてみた
1-1-3・ AuthのSignUpの通信エラー対応
1-2 ・ FirebaseAuthのログイン処理
1-3 ・ FirebaseAuthのログイン認証とログアウト処理
1-4 ・ FirebaseAuthのパスワード初期化処理
1-5 ・ FirebaseAuthのメールアドレスの有効化
1-6 ・ FirebaseAuthのメールアドレスとパスワード変更
1-7 ・ FirebaseAuthの表示名変更
1-8 ・ FirebaseAuthの拡張項目追加
1-9-1 ・ FirebaseAuthのTwitter認証
1-9-2 ・ FirebaseAuthのTwitter認証(既存ユーザー向け)と解除
1-10 ・ FirebaseAuthのGoogle認証(既存ユーザー含む)と解除 2-1・ FirebaseStorageのファイルアップ:基礎
2-2・ FirebaseStorageのファイルアップ前に画像の切り抜き
2-3・ FirebaseStorageのファイルアップの移動
3-1・FirestoreのCRUD
3-2・Firestoreのデータ取得補足
3-3・Firestoreのページネーション処理
3-4・Firestoreのコレクション(テーブル)のJOIN
4-1・Realtime Databaseでchatアプリ(書き込み) 今ここ
4-2・Realtime Databaseでchatアプリ(一覧取得)
5・ FirebaseのHosting(デプロイ)
6-1・ Cloud FunctionsでHello,world
6-2・ Cloud Functions(エミュレータ)でHello,world
6-3・ ユーザーの作成時にCloud Functionsを実行
6-4・ Cloud Functionsでメール送信
6-5・ Cloud Functionsでslackに通知する

よかったら他の記事も見てください。

やりたい事

  • chatアプリの作成
    • Realtimedatabaseに書き込み

やりたい事は以外とシンプル

Firebase Realtime Databaseとは

クラウドホスト型データベースです。データは JSON として保存され、接続されているすべてのクライアントとリアルタイムで同期されます。

  • データを 1 つの大きな JSON ツリーとして保存します。
  • シンプルなデータは非常に簡単に保存できます。
  • 複雑で階層的なデータについては、大規模な整理を行うことが難しいです。

クエリ

  • プロパティの並べ替えまたはフィルタリングを行うことはできますが、両方はできません。
  • デフォルトで多層型です。常にサブツリー全体を返します。
  • JSON ツリー内の個々のリーフノード値まで、あらゆる粒度でデータにアクセスできます。
  • インデックスを必要としません。ただし、データセットが大きくなるにつれて、特定のクエリのパフォーマンスは低下します。(WARNINGがでるので対応した方がいい)

書き込みとトランザクション

  • 設定オペレーションと更新オペレーションを通じてデータを書き込みます。
  • トランザクションは、特定のデータ サブツリーに対してアトミックです。   トランザクション処理には向かない

パフォーマンス

  • レイテンシが非常に低く、頻繁な状態同期に最適です。
  • 1つのデータベースで、約 200,000件の同時接続と毎秒 1,000 回の書き込みまでスケーリングが可能です。それ以上スケーリングするには、複数のデータベースにまたがるデータのシャーディングが必要になります。

レイテンシとはデータ転送における指標のひとつで、転送要求を出してから実際にデータが送られてくるまでに生じる、通信の遅延時間のことをいいます。 この遅延時間が短いことをレイテンシが小さい(低い)、遅延時間が長いことをレイテンシが大きい(高い)と表現しています。 https://www.idcf.jp/words/latency.html より引用

同時接続数は普通にシステムなら大丈夫なレベルです

事前作業

Realtime Databaseを有効化が必要です

FirebaseのWebコンソールへログインしたら、メニュー「Realtime Database」→「データベース作成」とたどります。

Firebase セキュリティ ルールを選択します。最初はテストモードで問題ないかと思います。あとでルールの設定はします

リージョンを選択していない人はここでリージョンの選択がでます。 リージョンは1度選択すると変更できないので慎重に選択する

プログラム

Firebaseの設定

他のstoreやStorageなどと同じです

import firebase from "firebase/app"
import "firebase/auth"
import "firebase/storage";
import 'firebase/firestore';
+ import 'firebase/database';

const firebaseConfig = {
    apiKey: process.env.REACT_APP_APIKEY,
    authDomain: process.env.REACT_APP_AUTHDOMAIN,
    databaseURL: process.env.REACT_APP_DATABASEURL,
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_APP_ID,
    measurementId: process.env.REACT_APP_MEASUREMENT_ID
};

firebase.initializeApp(firebaseConfig);
var auth_obj = firebase.auth();
if (process.env.REACT_APP_HOST === "localhost") {
    console.log("useEmulator:auth")
    auth_obj.useEmulator("http://localhost:9099")
} 
var db_obj = firebase.firestore();
if (process.env.REACT_APP_HOST === "localhost") {
  db_obj.useEmulator("localhost", 8080);
  console.log("useEmulator:firestore")
}
+ var database_obj = firebase.database();
+ if (process.env.REACT_APP_HOST === "localhost") {
+   database_obj.useEmulator("localhost", 9000);
+   console.log("useEmulator:RealtimeDatabase")
+ }
var storage_obj = firebase.storage();

export const Twitter = new firebase.auth.TwitterAuthProvider();
export const Google = new firebase.auth.GoogleAuthProvider();
export default firebase;
export const db = db_obj;
+ export const database = database_obj;
export const auth = auth_obj;
export const storage = storage_obj;

難しい所はないかと思います

ルーティングの設定

chatの書き込み用のルーティングを追加します

import {TextInput as chatTextInput }  from "./chat/TextInput"
function App() {
  return (
    <>
    <Router>
      <AuthProvider>
        <Switch>
     ・
     ・
          <AuthFirebaseRoute path="/chat/textinput" component={chatTextInput} />
     ・
        </Switch>
      </AuthProvider>
    </Router>
    </>
  );
}

単体でコンポーネントをテストするために設定しています。 最終的には消す予定です。

このプログラムは書き込み時にユーザーも書き込みのでAuthの情報が必要です。

Chatの書き込みプログラム

import React ,{ useState }from 'react'
import TextField from '@material-ui/core/TextField';
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import SendIcon from '@material-ui/icons/Send';
import Button from '@material-ui/core/Button';
import { useAuth } from "../../contexts/AuthContext"
import {database} from "../../firebase"
import moment from 'moment';

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    wrapForm : {
        display: "flex",
        justifyContent: "center",
        width: "95%",
        margin: `${theme.spacing(0)} auto`
    },
    wrapText  : {
        width: "100%"
    },
    button: {
        //margin: theme.spacing(1),
    },
  })
);

export const TextInput = () => {
    const [message, setMessage] = useState("")
    const { currentUser} = useAuth()   //Firebaseの共通変数と変更などの関数
    const handleKeyPress = (event: React.KeyboardEvent) => {
        if (event.keyCode === 13 || event.which === 13) {
            event.preventDefault()
            if (message === ""){
            } else {
                //console.log("handleKeyPress")
                handleMessageData()  
            }
        }
    };

    async function handleMessageData () {
        if (message === ""){
            console.log("message nothing")
        } else {
            console.log("handleMessageData start ")
            const displayName = currentUser.displayName ? currentUser.displayName : "名無しさん"
            const photoURL = currentUser.photoURL ? currentUser.photoURL : "/dummy.jpg"
            const uid = currentUser.uid ? currentUser.uid : "Guest"//本来ありえない
            //let timestamp = firebase.firestore.FieldValue.serverTimestamp()
            let timestamp = moment()
            //console.log(timestamp.format())
            database.ref('messages/' + timestamp.format('YYYYMMDDHHmmss') + uid ).set({
                uid: uid,
                displayName: displayName,
                photoURL : photoURL,
                message : message,
                createAt : timestamp.format('YYYY-MM-DD HH:mm:ss')
            }, (error) => {
                    if (error) {
                        console.log("error")
                        setMessage("errorになりました")
                    } else {
                        console.log("success")
                        setMessage("")
                    }
                }
            );
            
        }
    }


    const classes = useStyles();
    return (
        <>
            <form className={classes.wrapForm}  noValidate autoComplete="off">
            <TextField
                id="standard-text"
                label="メッセージを入力"
                className={classes.wrapText}
                value={message}
                onChange={(e) => setMessage(e.target.value)}
                onKeyPress={handleKeyPress}
            />
            <Button variant="contained" color="primary" className={classes.button} onClick={handleMessageData}  >
                <SendIcon />
            </Button>
            </form>
        </>
    )
}

プログラム解説

database.ref('messages/' + timestamp.format('YYYYMMDDHHmmss') + uid ).set({
    uid: uid,
    displayName: displayName,
    photoURL : photoURL,
    message : message,
    createAt : timestamp.format('YYYY-MM-DD HH:mm:ss')
}, (error) => {
        if (error) {
            console.log("error")
            setMessage("errorになりました")
        } else {
            console.log("success")
            setMessage("")
        }
    }
);
  • データベースに接続して書き込みをおこなています。1つのJSONファイルに書き込む形なのでテーブルという概念はないです。 あえてテーブルみたいな扱いをするなら messages/ の部分が該当します。

  • 成功と失敗はコールバックのerrorを見て判断しています。1回も失敗になった事がなく、エラーハンドリングができていません

結果

f:id:masalib:20201218175114p:plain

データを入力すると

f:id:masalib:20201218175228p:plain

できていない事

  • テキストしか入力できないです。LINEだと画像とかスタンプとか入れれます。作りたいけど難しい

感想

  • 書き込みは拍子抜けするほど簡単にできた