#背景
ReactnativeでAndroidアプリ開発をしています。
本当に初学者なので間違えている点あると思いますがご了承ください、、、
firebase & Reactnativeでの開発をしていてタイムラインを実装しようとしたときに
1.まずPostをcollectiongroupとして取得
2.そのPostそれぞれが持つuidを元にPostを投稿したUserを取得し、User情報と一緒にListにぶち込む
3.そのListをsetListとしてstateを変更してFlatListで表示する
という実装をしたかったのですが、FlatListをScrollViewの中に入れてしまったりするとどうも表示されず、setListもうまく機能していなかったので色々調べました
#ソースコード
うだうだ話していてもあれなので実際に実装したコードをとりあえず貼っておきます
useEffect(()=>{
firestore()
.collectionGroup('posts')
.onSnapshot(async(querySnapshot) => {
let tempList = [];
await Promise.all(querySnapshot.docs.map(async doc => {
let documentSnapshot=await firestore().collection('users').doc(doc.get('uid')).get();
tempList.push({
...doc.data(),
uid:doc.get('uid'),
uname:documentSnapshot.get('nickname'),
uimg:documentSnapshot.get('img'),
id: doc.id,
});
}));
setList(tempList);
});
},[]);
私はタイムラインをつくる、という目的だったのでuseEffect内での関数ですが、
上のような実装をすると無事postにデータが入った状態でScreenがRenderされるのでいい感じに表示できました!
詰まった所について解説しておきます。
##async awaitについて
初心者向けです。
まだ自分も初心者なので理解が浅く、非同期関数でつまずいてしまったので
同じような人の助けになればと思い書かせて頂きます。
asyncは何があってもPromiseを返す関数
例えば、
//これは1を返す
function number(){
return 1
}
//これは1をラップしたPromiseが返される
async function number(){
return 1
}
awaitはPromiseを返す関数の前につければ、その関数の処理が終わるまでその行で関数の実行を待っていてくれるやつ(ただしasync関数内でしか使えない)
例えば、
async function a(){
//firestoreからなんか取ってくるとかの重い処理をするとこの行↓で実行が止まってくれる!
let users = await firestore().collection('users').get()
//上のawaitの関数の結果が返ってきてからconsole.logが動く!
console.log(users)
}
といった感じで考えてもらえるとよいかと思います。(間違っていたらすみません!)
じゃあPromiseとは?てなる人がいるかもしれません
これも私の理解が浅いのですが、Promiseは.thenとか.catchとかを関数の後に続けることができて、前の結果を受けて次にどんな処理をするのか、というのを決めることができるという返り値です。
firestore().collection('users').get()
.then(()=>{})
.catch(()=>{})
的な感じです多分、、、
Promiseがあることで何かの処理を終えた後で何かの処理を行うという非同期処理を行うことができるのです。
このPromiseを使いやすくしたのがasync awaitで、awaitを使えばPromiseのときのように.thenなどを使わずにどの処理を待てばよいかわかる、というようなものだと思います、多分、、、
##流れ
実際に流れを見ていくと
useEffect(()=>{
firestore()
.collectionGroup('posts')
.onSnapshot(async(querySnapshot) => {
let tempList = [];
まずここまではuseEffect内でpostsのcollectionGroupを取ってきて.onSnapshotでその結果であるquerySnapshotを用いて関数を実行しています。
ここでasyncを用いているのはこの関数内でawaitを使いたい(処理を待たせたい)関数があるため、awaitを使うためにasync関数にしています。
let tempList=[]はpostsの情報を一時的に入れておくための箱を用意してるだけです。
.onSnapshot(async(querySnapshot) => {
let tempList = [];
await Promise.all(querySnapshot.docs.map(async doc => {
その後ですが、async関数内でawaitを使いたいものとは、Promise.allというものです。
この関数は複数のPromiseを返す関数を平行して処理してPromiseを返す、というものです。もちろんPromiseを返してくれるのでawaitをつけることができます。
次にPromise.allが処理する関数はなにかといわれると、引数のquerySnapshot.docs.map(async doc =>{...となるのですが、
ここではquerySnapshotが持つdocumentひとつひとつに対してそれらを引数としたasync関数を実行している、という感じです。
また先ほどとは違い、ここでasyncを使っているのはawaitを使いたいだけでなく、
async関数は必ずPromiseを返すので、Promise.allで実行することができる、ということも考えています。
.onSnapshot(async(querySnapshot) => {
let tempList = [];
await Promise.all(querySnapshot.docs.map(async doc => {
let documentSnapshot=await firestore().collection('users').doc(doc.get('uid')).get();
tempList.push({
...doc.data(),
uid:doc.get('uid'),
uname:documentSnapshot.get('nickname'),
uimg:documentSnapshot.get('img'),
id: doc.id,
});
}));
setList(tempList);
});
},[]);
そしてそのあとですが、ここで2回目のfirestoreの呼び出しが起こります。
ここで注意してほしいのが、firestore().collection().doc().onSnapshotは使えない、ということです。
さっきは.onSnapshotを使っていたのに今回はなぜだめなのか、となるかと思います(なってほしい笑)
いま、このasync関数の中では、
- 処理の重いfirestoreへの呼び出し
- tempListへのデータのpush
の2つのことが行われようとしています。
このときfirestoreへの呼び出しができてからtempListにデータをpushしたいのですが、そのためには1の段階で処理を止めないといけません。
そのためasyncを使いたいのですが、asyncはPromiseを返す関数にしか使えないので、.onSnapshotとしてしまうと間違いなのです。
そこで、get()メソッドを用います。get()はPromiseを返してくれるのでawaitを用いることができ、非同期でDocumentSnapshotを取得することができるのです!
*ちなみに.onSnapshotとするとawaitがつけられないので処理をしただけで結果が返ってこないまま進むので結果的に空のtempListをsetListすることになり表示されません
かくしてtempListにデータが無事入り、setListでstateにセットすることができたのでFlatlist等で表示できるかと思います!
#参考にさせていただいたサイト
Firestoreのデータをasync/await, mapで取り出したい
Async/await - 現代の JavaScript チュートリアル
firebase公式サイト様様