目的・環境
- Firebase→React-native→FlatListの構成でページネーションを実装する
- 読み込み方法はInfinite Scrollを行う
- 既存の日本語の記事では、Reduxを交えた高度な構成であったり、React-nativeのFlatListを利用した事例がなかったり、何より、最小限のシンプルな例が見当たらなかったため、整理することにした
ゴール
- Firebaseでページネーションを行う
- React-natigve、アプリでスクロールで更新する
-
FlatListを使う <ーこれを重視しました。よく使うリストの作り方だと思うので - 最小限の部分を把握する
実装
概要
- Firebaseに、createtAt でタイムスタンプを設定している。そのタイムスタンプ降順でソートする
今回はタイムスタンプだが、idでもなんでもソートできればいい - 一度の読み込みサイズは2レコード
- ページ下部到達で、追加の2レコードを表示する
- 読み込み中はインジケータを表示する
コードと解説
- ポイント
- stateに最後に読んだデータを記録しておく ->lastVisible
- lastVisibleは、orderbyでソート軸にした項目
- クエリの条件で
.startAfter(lastVisible)を追加しカーソルを設定
言うまでもなく.limit(バッチサイズ)で絞る - FlatListに属性を追加する
onEndReached={this.fetchMore} onEndReachedThreshold={0} - リストが最下部にきたら
onEndReachedで指定したメソッドが実行される。そこで再読み込み処理を書く
app.js
class サンプルクラス extends React.Component {
state = {
items: [],
refreshing: false,
limit: 2, // 今回はここに読み込むバッチサイズを定義しておく。
lastVisible: null,
loading: false,
}
constructor(props) {
super(props);
this.listLoading(props); // 初回ロードを実行
}
// 初回ロード リストを遅延ローディングする
// ここは単純に画面表示直後のデータを取り込んでいるだけ
// チェックするポイントは以下
listLoading = async (props) => {
// 読み込み中を開始する
this.setState({
loading: true,
});
const firestore = firebase.firestore();
await firestore.collection"コレクション")
// 今回は、ソート順を、作成時刻降順、としているため、タイムスタンプを利用
.orderBy("createdAt", "desc")
.limit(limit).then((items) => {
// 取得結果の最後をstateに保持する
let lastVisible = items[items.length - 1].createdAt;
this.setState({
items : items, //リストに表示するデータ
loading: false, // 読み込み中を完了する
lastVisible:lastVisible, // 現在読み込んでいる最後のデータを保持する(次に読み込むとき、「これより後」と絞る)
})
})
// 再読み込み リストが最下部に到着したら読み込み
fetchMore =async () => {
if(this.state.refreshing){ // すでにrefreshingが実行されていたら抑止する 注釈1)
console.log("二重読み込み抑止")
return
}
console.log('追加読み込み開始');
this.setState({
refreshing: true,
});
const firestore = firebase.firestore();
let additionalQuery = await firestore.collection("コレクション")
.orderBy("createdAt", "desc")
// 前回読み込んだ最後をカーソルに設定し、そこから、limitまで取得する
// orderByで定義した方向にlimit分取得する
// lastVisibleは、orderByで定義した項目と同じものを設定する
.startAfter(this.state.lastVisible)
.limit(limit)
let documentSnapshots = await additionalQuery.get()
let items = []
documentSnapshots.forEach(function(doc) {
var res = doc.data();
items.push(res);
})
// 追加取得したデータの最後のデータをstateに記録する
// orderBy("createdAt", "desc") としているのでcreatedAtを保持する
let lastVisible = items[items.length - 1].createdAt;
// 取得したデータを、既存のリストのデータの後ろにつける
let itemsList = this.state.items
for(let item of items) {
itemsList.push(item)
}
// stateを更新し、loding状態を終了する
this.setState({
items : itemsList,
loading: false,
lastVisible:lastVisible,
refleshing:false // リフレッシュ状態を終了
})
}
// ローディングのインジケータ 下部ページング
renderFooter = () => {
if (this.state.refreshing) {
return <ActivityIndicator size="large" />;
} else {
return null;
}
};
render() {
return(
<View>
<FlatList
onEndReached={this.fetchMore} // 表示末端まで到達時に、呼び出し
onEndReachedThreshold={0.1} // 感度を指定。再読み込みを行う閾値を設定する
ListFooterComponent={this.renderFooter} //画面下部のインジケータを表示
refreshing={this.state.refreshing}
data={this.state.items}
extraData={this.state.refreshFlatList}
renderItem={this.getItem}
keyExtractor={item => item.id}
/>
</View>
)
}
}
- 注釈1)
- リフレッシュした結果、リストに重複した行が挿入されてしまう問題
- 原因は、インフィネートスクロールの読み込みイベントがダブって実行されてしまっていること。処理が完了してlastVisibleが更新される前に再実行されてしまい、結果が重複する
- 対策は、すでにローディングに入っていたら、それが完了するまで、ローディング処理に入れないようにする
参考資料
- もっともわかりやすかった資料
Implementing Infinite Scroll (Lazy Loading) With FlatList Using React Native + Firebase Cloud Firestore
https://levelup.gitconnected.com/react-native-firebase-cloud-firestore-implementing-infinite-scroll-lazy-loading-with-flatlist-a9e942cf66c6
- 公式の関連するドキュメント
クエリカーソルを使用したデータのページ設定
https://firebase.google.com/docs/firestore/query-data/query-cursors?hl=ja
- その他
Cloud Firestore でのデータの並べ替えと制限
https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja
コピペで使うFirebase【Firestore ページング編】
https://qiita.com/ovama-koffee/items/c025e3d7d888c4a9a6e3