弊社ではiOS, Androidアプリの開発にReact Native + Expoを使用していますが、非常に開発効率が高く、その恩恵を日々感じています。
今回はそのバックエンドにFirebaseの提供する「Cloud Firestore」を利用して簡単なアプリを作ってみようと思います。
せっかくなので何か役に立つものを作りたいのですが、
ここは個人的な趣味で...
最近読んだ本 筋トレビジネスエリートがやっている最強の食べ方
の食事摂取を実行するためにレコーディングダイエット用アプリを作りたいと思います。
Cloud Firestoreとは
Firebaseの提供するデータベースです。
- リアルタイム
- スキーマレス
という特徴があります。
ここまで聞くと、FirebaseのRealtime Databaseと何が違うの?
と思いますが、ざっくりいうとFirestoreはRealtime Databaseの後継になります。
使ってみて感じた違いとしては、Realtime Databaseのデータモデルは基本的に平坦な構造にすることになりますが、Firestoreでは階層化して整理できます。
この辺がRDBになれた身にとっては取っ付きやすい印象でした。
また検索クエリもFirestoreではだいぶ柔軟に組めるようです。
FirebaseとCloud Firestoreの設定
では早速Cloud Firestoreの設定からはじめましょう!
Firebaseのアカウントを作成したら、まずデータベースを作成します。
セキュリティルールはとりあえずテストモードを選びます。
続いて認証の設定です。
お手軽な匿名を選びます。
最後に「ウェブアプリにFirebaseを追加」を選びます。
こちらの情報は後ほどの実装の際に使用します。
Expoでプロジェクトを作成する
続いて、Expo XDEで新規プロジェクトを作成します。
テンプレートは「Tab Navigation」を選びました。
そして早速テンプレートを立ち上げます。
デフォルトでここまで出来ています。素晴らしい🎉
データモデルの設計
さてコーディングの前にデータモデルについて考えてみましょう。
Cloud Firestoreには「ドキュメント」と「コレクション」という概念があります。
Cloud Firestore は NoSQL ドキュメント指向データベースです。SQL データベースとは違い、テーブルや行はありません。代わりに、データは「ドキュメント」に格納し、それが「コレクション」にまとめられます。
Cloud Firestore Data model | Firebase
この概念図がわかりやすいと思いました。
ドキュメントが中にデータを持っていて、そのドキュメントを束ねるフォルダがコレクションになります。
またドキュメントとコレクションは階層化することができます。サブコレクションといって、サブコレクションは特定のドキュメントに紐付けるすことが出来ます。
なので コレクション>ドキュメント>コレクション>ドキュメント...と交互になるように階層化されます。
レコーディングダイエットのデータモデル
さて今回はレコーディングダイエットのアプリを作ろうと思います。
日々、食べたものを記録するやつです。
要件は...
- ユーザー毎に食べた物を記録できる
- 食べ物データには、名前、日付、カロリー、タンパク質、脂質、炭水化物、を記録できる
- 日毎に摂取量の合計を表示できるようにする
のようにしたいと思います。
そこで今回は以下のようなデータモデルを考えました。
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() { // Check if we are signed in... if (Fire.shared.uid) { // If we are, then we can get the first 5 posts const res = await Fire.shared.getFoods(); } else { // If we aren't then we should just start observing changes. This will be called when the user signs in 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ができました。
マージンとかがちょっとアレですが、とりあえずフォームとボタンがあるので動作確認はできそうです。
登録ボタンを押すとcreateFood()
メソッドを実行し、フォームに記入したデータをFirebaseに書き込んでくれるはずです。
データを登録してみる
では早速データの登録を試してみましょう!
適当に記入して...
登録をポチッ...
Cloud Firestoreにデータが格納されました🎉
おわりに
ここまでで、React Native + Expoな環境でCloud Firestoreを扱うための、ベース部分についてご紹介しました。
Realtime Databaseも使ったことがありますが、データ構造の設計がやりやすいように感じました。
また、料金的にもよほどのアクセス量にならない限り、無料で使えるので、個人でアプリを作る際などには、
React Native + Expo + Cloud Firestoreの組み合わせはかなりアリだと思いました。
後編では、格納したデータを読み込んで表示したり、検索したり、ってを書きたいと思います!
【PR エンジニア絶賛募集中!】
Ducklings(ダックリングズ)は、ウェディング ✕ ITの領域に特化したスタートアップです。
花嫁向けコミュニティアプリ「maricuru-マリクル」や、結婚式の360°VR記録映像プラットフォーム「HUG WEDDING」を開発しています。
現在はmaricuruの開発にあたり、React NativeやRailsを主に扱っています。
興味のある方は @takahi5 までDM下さい♪
週末だけの副業やインターンも歓迎です!
React Nativeなどの勉強会も開催しているので、ご興味のある方はチェックしてみてください。 https://ducklings.connpass.com/