Firestoreで多量のデータを作り、ReactNativeのコンポーネントを使って以下のような簡単な無限スクロールを実装してみます。
テストデータの作成
前の記事の続きになります。
Cloud Functionsを使って、認証ユーザーが作成された時に多量のデータをユーザーに紐付けて作成してみます。
今回はauthor
(ユーザーのuid)、text
、order
というフィールドをもつposts
コレクションを追加しました。
const db = admin.firestore();
exports.createUser = functions.auth.user().onCreate((user) => {
const SIZE = 100;
const batch = db.batch();
const userRef = db.collection('/users').doc(user.uid);
batch.set(
userRef,
{
email: user.email,
postsSize: SIZE
}
);
for (let i = 0; i < SIZE; i++) {
batch.set(
userRef.collection('/posts').doc(),
{
text: 'test',
order: i
}
);
}
batch.commit();
});
データベースのセキュリティルールはこのように設定しておきます。
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
match /posts/{post} {
allow read, write: if request.auth.uid == userId;
}
}
}
}
100個なのでそんなに多量ではないですが、Firestore.batch
を使って、users
の中のサブコレクションとして一括書き込みをするようにしました。
Firestoreでは低コストでドキュメント数をカウントする関数が無いようなので、posts
の数をusers
のフィールドとして保持しておいて、追加の度にインクリメントするようなのを想定しています。
これをデプロイし、ユーザー登録してみます。
ユーザー情報にposts
コレクションが作られ、テストデータが保存されました。
では、一覧画面を作ります。
import React, { Component } from 'react';
import { View, FlatList, ActivityIndicator } from 'react-native';
import { ListItem } from 'react-native-elements';
import { connect } from 'react-redux';
import { getPosts } from '../redux';
class List extends Component {
constructor(props) {
super(props);
this.startLoading = this.startLoading.bind(this);
this.state = {
isLoading: false
};
}
componentDidMount() {
const { posts } = this.props;
if (!posts) {
this.startLoading();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.posts !== this.props.posts) {
this.setState({
isLoading: false
})
}
}
startLoading() {
const { posts, getPosts } = this.props;
this.setState({
isLoading: true
}, () => {
getPosts(20, (posts && posts.length) ? posts[posts.length - 1] : null);
});
}
render() {
const { dbUser, posts } = this.props;
const { isLoading } = this.state;
const isMore = posts && posts.length < dbUser.postsSize;
const isLoadingEnabled = !isLoading && isMore;
const activityIndicator = isMore ? (
<ActivityIndicator
animating
size="large"
style={{
marginTop: 16,
marginBottom: 16
}}
/>
) : null;
return (
<View
style={{
flex: 1,
height: '100%'
}}
>
{posts ? (
<FlatList
data={posts}
keyExtractor={(item) => (item.id)}
renderItem={({ item }) => (
<ListItem
title={item.data().order.toString()}
subtitle={item.data().text}
bottomDivider
/>
)}
onEndReached={isLoadingEnabled ? this.startLoading : null}
onEndReachedThreshold={0}
ListFooterComponent={activityIndicator}
/>
) : null}
</View>
);
}
}
const mapStateToProps = state => ({
dbUser: state.user.dbData,
posts: state.user.posts
});
const mapDispatchToProps = {
getPosts
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(List);
ReduxのstoreからDBのユーザー情報、DBのテストデータを取って来るとして、コンポーネントが初期化されたらデータの取得を開始するアクションを呼ぶようにします。
一覧画面の描画にはreact-nativeのFlatList
とreact-native-elementsのListItem
を使ってみます。
FlatList
のonEndReached
にリストの一番下までスクロールした時のトリガー関数を指定できるので、そのタイミングでもデータ取得を開始するようにします。ローディングの状態変化やそれ以上読み込み可能かどうかの判断など、実装パターンは大体何を使うのでも同じだと思うので、好きな書き方でよさそうです。
onEndReached
が呼ばれるタイミングはonEndReachedThreshold
で変えることができ、リストの一番下からの距離を、リストの見える範囲の高さ(全体の高さではなく)を1として指定できます。デフォルトは0.5ですが今回は0を指定し、一番下まで完全にスクロールしてから読み込むようにしてみました。
getPosts
アクションはこのようにしました。
export const getPosts = (size, startAfter) => (dispatch) => {
const user = store.getState().user.data;
if (user) {
const userRef = db.collection('/users').doc(user.uid);
userRef.get()
.then((userSnapshot) => {
dispatch({
type: 'SUCCESS_GET_USER',
data: userSnapshot.data()
});
let postsRef = userRef.collection('/posts')
.orderBy('order')
.limit(size);
if (startAfter) {
postsRef = postsRef.startAfter(startAfter);
}
return postsRef.get();
})
.then((querySnapshot) => {
dispatch({
type: 'SUCCESS_GET_POSTS',
posts: querySnapshot.docs
});
})
.catch(({ message }) => {
Alert.alert(message);
});
}
};
読み込む個数と、最後に読み込んだドキュメント(DocumentSnapshot
)を指定して取得を開始します。
Query
のorderBy
、limit
、startAfter
を使って整列、個数指定、位置指定を行っています。
サブコレクションに対するQuery
はDocumentReference
にさらにcollection
メソッドを使用することで取得できます。
これでFirestoreでのページング処理ができました。
Github
サンプルソースはこちらです
https://github.com/mildsummer/expo-auth-practice
https://github.com/mildsummer/expo-auth-practice-firebase