Edited at

FirebaseのCloud Functionsで、Cloud Firestoreによるトリガーじゃない関数でFirestoreにデータを保存する

Firebaseとかいうスーパー便利サービスの使い方の一部を紹介します。


Cloud Functions

従来、自分で借りたり用意したサーバー上で動かしていたもろもろの処理を、Firebase上に書いてしまうことができます。

サーバー用意しなくていいし、少量なら無料だし、関数をトリガーも使いやすいものがたくさん用意されています。

今日は、Cloud Functionsを使って、Analytics for Firebaseへのイベントの投稿をトリガーにして、Cloud Firestoreにデータを保存するようなfunctionを書いてみようと思います。


そんなの公式ページにサンプルあるでしょ?

ある…かな?

こちらのCloud Functions で可能な処理には、「Realtime Database(Cloud Firestoreの前身)へのデータの追加をトリガーにして、その追加されたデータに何かする」という例はありますが、新しいデータをCloud Firestoreに追加する例はパッと見つからなかったです。


コード

いきなりですがコードです。中身については下で順次説明していきます。


functions/src/index.ts

import * as functions from 'firebase-functions';

import * as admin from 'firebase-admin';//※1 Admin SDK について
admin.initializeApp();//※1 Admin SDK について

exports.recordPostedScore = functions.analytics.event('post_score').onLog((event)=>{
const firestore = admin.firestore();//※2 timestampについて
const settings = { timestampsInSnapshots: true };//※2 timestampについて
firestore.settings(settings);//※2 timestampについて
const colref = firestore.collection('scores');
colref.add({
score: Math.round(Number(event.params.score)),
date: new Date(),
device_category: event.user.deviceInfo.deviceCategory,
device_model: event.user.deviceInfo.deviceModel
}).then(docref=>{
console.log("Added ID is: " + docref.id);
}).catch(e=>console.log(e));
});



AdminSDKについて

import * as admin from 'firebase-admin';//※1 Admin SDK について

admin.initializeApp();//※1 Admin SDK について

この部分です。

普段クライアント側でコーディングしてる場合、Firestoreにデータを追加するには、CollectionReferenceとかDocumentReferenceを作成しないといけないのですが、

その辺のことができるのがこのAdmin SDKです。

こちらの公式スタートガイドに書いてあるように、admin.initializeApp();を実行しておく必要があります。importの直後にやっておけば間違いないですね。

また、この公式スタートガイドはimport文のかわりにrequire文が書かれており、若干書き方が古いです(2018-12-20現在)。

ターミナルからfirebase init functionsとした場合に作成されるfunctions/src/index.tsにはこの記事にあるような書き方でimport文が書かれておりましたので、そちらの方が新しくて間違いないと思います。


const firestore = admin.firestore();

const firestore = admin.firestore();

これで、こちらのGoogle Cloudのドキュメントに書いてあるのと同じFirestore clientが得られますので、あとは

colref = firestore.collection('scores');

などとすれば、CollectionReferenceが得られるので、クライアント側でプログラミングしてる時と同じように書けます。

admin.firestore()について詳しくはこちら。→本家リファレンス


timestampについて

Cloud Firestoreに保存するデータのうち、timestamp型のものは、new Date()をそのままつっこんではいけないようです。

そのために、こちらの

  const firestore = admin.firestore();//※2 timestampについて

const settings = { timestampsInSnapshots: true };//※2 timestampについて
firestore.settings(settings);//※2 timestampについて

下二行のような設定作業が必要になります。

詳しくはこちらのQiita記事がわかりやすかったです。

FirestoreのTimestampの仕様変更による警告と、その対処


paramsとかdeviceCategoryとかの調べ方

あともう一つ困ったことがあったので、それについての話だけして終わります。


リファレンスを見るときは、一つずつ、何型のインスタンスが返るのか確認し、その型の使い方を改めて調べることを繰り返す

ということです。


実際にやってみましょう。

今回は、Analytics for Firebaseへのイベント投稿をトリガーとして、「そのイベントについてきたscore値」「投稿したユーザーのデバイス情報」をCloud Firestoreに保存したいです。

でも、それらの情報が一体どういう形で取得できているのかがわからない!!

ちなみにいきなりですが最終的なコードは以下のようになります。

exports.recordPostedScore = functions.analytics.event('post_score').onLog((event)=>{

//...中略...
colref.add({
score: Math.round(Number(event.params.score)),
date: new Date(),
device_category: event.user.deviceInfo.deviceCategory,
device_model: event.user.deviceInfo.deviceModel
}//...後略...
);

この部分、大枠としてはfunctions.analytics.event(...).onLog(...)の引数に、ハンドラ関数を渡す、という形をしています。

Analytics for Firebaseへのイベント投稿をトリガーとして呼び出される関数は、こちらの本家の例によると、functions.analytics.event('event_type').onLog((event)=>{});という書き方をするようなのて、これでいい。

で、そのハンドラ関数の引数に、いろいろな情報が詰まったインスタンスを渡してくれるわけですね。

ところで、そのハンドラ関数の引数はeventという名前にしてあるわけですが、このeventには一体何型のインスタンスが渡っているんでしょうか?どう書けばそこから情報を取り出せるのでしょうか?

ということでfunctions.analyticsのリファレンスを見ると、functions.analytics.event(...)というメソッドは

functions.analytics.AnalyticsEventBuilderを返すことがわかります。

では、functions.analytics.AnalyticsEventBuilderのリファレンスを見ると、

そこにやっとonLogメソッドがあって、その返り値はfunctions.CloudFunctionだそうです。なるほど、これでCloud Functionsで実行可能な形になるわけですね。

で、そのonLogの引数として渡すハンドラ関数の引数は、何型なのかというと、

functions.analytics.AnalyticsEvent
だそうです。

やっとこれでeventの型がわかりました。

で、

functions.analytics.AnalyticsEventのリファレンスを見るとparamsとかuserとかのプロパティがあって、その中に今回記録したい情報があるらしいことが、ここまで調べてようやくわかりました

さらに、userの中にどんな情報があるかは

functions.analytics.UserDimensionsを見る必要があるし、その中のデバイス情報についてはさらに

functions.analytics.DeviceInfo
を見なきゃいけません。そこまで見てやっと返り値がstring or undefinedになります。

サンプルコード一つでポンとわからない場合、この程度のリファレンスの渡り歩きは日常茶飯事ですから、臆せずガンガン調べましょうね〜〜。