React Native Tech Blog

supported by maricuru (旧maricuru tech blogです)

React Native入門: FirebaseのCloud Firestoreでレコーディングダイエットアプリを作ってみる(前編)

弊社ではiOS, Androidアプリの開発にReact Native + Expoを使用していますが、非常に開発効率が高く、その恩恵を日々感じています。

今回はそのバックエンドにFirebaseの提供する「Cloud Firestore」を利用して簡単なアプリを作ってみようと思います。

f:id:wasan:20180803112459p:plain

せっかくなので何か役に立つものを作りたいのですが、
ここは個人的な趣味で...
最近読んだ本 筋トレビジネスエリートがやっている最強の食べ方
の食事摂取を実行するためにレコーディングダイエット用アプリを作りたいと思います。

Cloud Firestoreとは

Firebaseの提供するデータベースです。

  • リアルタイム
  • スキーマレス

という特徴があります。
ここまで聞くと、FirebaseのRealtime Databaseと何が違うの?
と思いますが、ざっくりいうとFirestoreはRealtime Databaseの後継になります。

使ってみて感じた違いとしては、Realtime Databaseのデータモデルは基本的に平坦な構造にすることになりますが、Firestoreでは階層化して整理できます。
この辺がRDBになれた身にとっては取っ付きやすい印象でした。

また検索クエリもFirestoreではだいぶ柔軟に組めるようです。

FirebaseとCloud Firestoreの設定

では早速Cloud Firestoreの設定からはじめましょう!

Firebaseのアカウントを作成したら、まずデータベースを作成します。 f:id:wasan:20180802170747j:plain:w400

セキュリティルールはとりあえずテストモードを選びます。
f:id:wasan:20180802170805j:plain:w400

続いて認証の設定です。
f:id:wasan:20180802170814j:plain:w400

お手軽な匿名を選びます。
f:id:wasan:20180802170833j:plain:w400

最後に「ウェブアプリにFirebaseを追加」を選びます。
f:id:wasan:20180802170844j:plain:w400

こちらの情報は後ほどの実装の際に使用します。
f:id:wasan:20180802170853j:plain:w400

Expoでプロジェクトを作成する

続いて、Expo XDEで新規プロジェクトを作成します。
テンプレートは「Tab Navigation」を選びました。

f:id:wasan:20180802171337p:plain:w400

そして早速テンプレートを立ち上げます。
デフォルトでここまで出来ています。素晴らしい🎉
f:id:wasan:20180802171436j:plain:w300

データモデルの設計

さてコーディングの前にデータモデルについて考えてみましょう。

Cloud Firestoreには「ドキュメント」と「コレクション」という概念があります。

Cloud Firestore は NoSQL ドキュメント指向データベースです。SQL データベースとは違い、テーブルや行はありません。代わりに、データは「ドキュメント」に格納し、それが「コレクション」にまとめられます。

Cloud Firestore Data model  |  Firebase

この概念図がわかりやすいと思いました。
f:id:wasan:20180802174423p:plain:w300

ドキュメントが中にデータを持っていて、そのドキュメントを束ねるフォルダがコレクションになります。

またドキュメントとコレクションは階層化することができます。サブコレクションといって、サブコレクションは特定のドキュメントに紐付けるすことが出来ます。

なので コレクション>ドキュメント>コレクション>ドキュメント...と交互になるように階層化されます。

レコーディングダイエットのデータモデル

さて今回はレコーディングダイエットのアプリを作ろうと思います。
日々、食べたものを記録するやつです。

要件は...

  • ユーザー毎に食べた物を記録できる
  • 食べ物データには、名前、日付、カロリー、タンパク質、脂質、炭水化物、を記録できる
  • 日毎に摂取量の合計を表示できるようにする

のようにしたいと思います。

そこで今回は以下のようなデータモデルを考えました。

users(コレクション) > user(ドキュメント) > foods(コレクション) > food(ドキュメント)

こういった感じでコレクションとドキュメントが交互になります。

Firebaseを扱うクラス

さていよいよ実装です!

初めに公式のFirebase Web SDKをインストールします。

yarn add firebase

続いてFirebase関連は以下のFire.jsに集約するようにします。

import firebase from "firebase";
import "firebase/firestore";

class Fire {
  constructor() {
    // 先程取得した情報を入れる
    firebase.initializeApp({
      apiKey: "xxxxxxxxxxxxxx",
      authDomain: "xxxxxxxxxxxxxx",
      databaseURL: "xxxxxxxxxxxxxx",
      projectId: "xxxxxxxxxxxxxx",
      storageBucket: "xxxxxxxxxxxxxx",
      messagingSenderId: "xxxxxxxxxxxxxx"
    });
    firebase.firestore().settings({ timestampsInSnapshots: true });

    firebase.auth().onAuthStateChanged(async user => {
      if (!user) {
        await firebase.auth().signInAnonymously();
      }
    });
  }

  createFood = ({ name, cal, protein, lipid, carbohydrate, date }) => {
    const createdAt = Date.now();
    this.foodCollection.add({
      name,
      cal,
      protein,
      lipid,
      carbohydrate,
      date,
      createdAt
    });
  };


  // Helpers
  get userCollection() {
    return firebase.firestore().collection("users");
  }

  get foodCollection() {
    return this.userCollection.doc(this.uid).collection("foods");
  }

  get uid() {
    return (firebase.auth().currentUser || {}).uid;
  }
}

Fire.shared = new Fire();
export default Fire;

要点を解説します。

初期設定

先程取得した情報を入れて下さい。

    firebase.initializeApp({
      apiKey: "xxxxxxxxxxxxxx",
      authDomain: "xxxxxxxxxxxxxx",
      databaseURL: "xxxxxxxxxxxxxx",
      projectId: "xxxxxxxxxxxxxx",
      storageBucket: "xxxxxxxxxxxxxx",
      messagingSenderId: "xxxxxxxxxxxxxx"
    });

usersコレクション

データのルートになるusersコレクションを設定しています。

  get userCollection() {
    return firebase.firestore().collection("users");
  }

foodsコレクション

usersコレクションの下にfoodsコレクションを設定しています。
userドキュメントのキーには、そのユーザーのuidを使用しています。

キーはそのコレクション内でユニークである必要があります。

  get foodCollection() {
    return this.userCollection.doc(this.uid).collection("foods");
  }

foodドキュメントの挿入

foodドキュメントを挿入するメソッドです。
foodCollection.add()で自動的にユニークなキーを設定してドキュメントを挿入してくれます。

任意のキーを設定したい場合はfoodCollection.doc('myKey')のようにして指定することもできます。

  createFood = ({ name, cal, protein, lipid, carbohydrate, date }) => {
    const createdAt = Date.now();
    this.foodCollection.add({
      name,
      cal,
      protein,
      lipid,
      carbohydrate,
      date,
      createdAt
    });
  };

UIを作る

続いてUIを作りましょう!

UIコンポーネントにはreact-native-elementsを使いました。
手軽にそれっぽいUIが作れるので便利です。

import React from "react";
import { StyleSheet, View } from "react-native";
import { FormLabel, FormInput, Button } from "react-native-elements";
import Fire from "../utils/Fire";
import firebase from "firebase";

export default class HomeScreen extends React.Component {
  static navigationOptions = {
    header: null
  };

  state = {
    name: "",
    cal: 0,
    protein: 0,
    lipid: 0,
    carbohydrate: 0,
    date: "2018-07-01"
  };

  async componentDidMount() {
    if (Fire.shared.uid) {
      const res = await Fire.shared.getFoods();
    } else {
      firebase.auth().onAuthStateChanged(async user => {
        if (user) {
          const res = await Fire.shared.getFoods();
        }
      });
    }
  }

  createFood() {
    const { name, cal, protein, lipid, carbohydrate, date } = this.state;
    Fire.shared.createFood({
      name,
      cal,
      protein,
      lipid,
      carbohydrate,
      date
    });
  }

  render() {
    return (
      <View style={styles.container}>
        <FormLabel>名前</FormLabel>
        <FormInput
          onChangeText={name => this.setState({ name })}
          value={this.state.name}
        />
        <FormLabel>カロリー(kcal)</FormLabel>
        <FormInput
          onChangeText={cal => this.setState({ cal: Number(cal) })}
          keyboardType={"number-pad"}
          value={this.state.cal.toString()}
        />
        <FormLabel>タンパク質(g)</FormLabel>
        <FormInput
          onChangeText={protein => this.setState({ protein: Number(protein) })}
          keyboardType={"number-pad"}
          value={this.state.protein.toString()}
        />
        <FormLabel>脂質(g)</FormLabel>
        <FormInput
          onChangeText={lipid => this.setState({ lipid: Number(lipid) })}
          keyboardType={"number-pad"}
          value={this.state.lipid.toString()}
        />
        <FormLabel>炭水化物(g)</FormLabel>
        <FormInput
          onChangeText={carbohydrate =>
            this.setState({ carbohydrate: Number(carbohydrate) })
          }
          keyboardType={"number-pad"}
          value={this.state.carbohydrate.toString()}
        />
        <Button title="登録" onPress={() => this.createFood()} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff"
  }
});

上記のコードでこんなUIができました。
マージンとかがちょっとアレですが、とりあえずフォームとボタンがあるので動作確認はできそうです。

f:id:wasan:20180802181741p:plain:w200

登録ボタンを押すとcreateFood()メソッドを実行し、フォームに記入したデータをFirebaseに書き込んでくれるはずです。

データを登録してみる

では早速データの登録を試してみましょう!

適当に記入して...
f:id:wasan:20180802182349p:plain:w200

登録をポチッ...

Cloud Firestoreにデータが格納されました🎉

f:id:wasan:20180802182544p:plain:w400

おわりに

ここまでで、React Native + Expoな環境でCloud Firestoreを扱うための、ベース部分についてご紹介しました。

Realtime Databaseも使ったことがありますが、Cloud Firestoreのほうがデータ構造の設計がやりやすいように感じました。

また、料金的にもよほどのアクセス量にならない限り、無料で使えるので、個人でアプリを作る際などには、
React Native + Expo + Cloud Firestoreの組み合わせはかなりアリだと思いました。

後編では、格納したデータを読み込んで表示したり、検索したり、ってを書きたいと思います!

参考

Instagram Clone (!) using Firebase, React Native & Expo.