Help us understand the problem. What is going on with this article?

ReactNative + Firestoreで簡単な無限スクロールページ

Firestoreで多量のデータを作り、ReactNativeのコンポーネントを使って以下のような簡単な無限スクロールを実装してみます。
スクリーンショット 2019-10-21 11.13.27.png

テストデータの作成

前の記事の続きになります。
Cloud Functionsを使って、認証ユーザーが作成された時に多量のデータをユーザーに紐付けて作成してみます。
今回はauthor(ユーザーのuid)、textorderというフィールドをもつpostsコレクションを追加しました。

functions/index.js(部分)
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のフィールドとして保持しておいて、追加の度にインクリメントするようなのを想定しています。
これをデプロイし、ユーザー登録してみます。
expo-auth-practice_–_Database_–_Firebase_console.png
ユーザー情報にpostsコレクションが作られ、テストデータが保存されました。

では、一覧画面を作ります。

List.js
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を使ってみます。

FlatListonEndReachedにリストの一番下までスクロールした時のトリガー関数を指定できるので、そのタイミングでもデータ取得を開始するようにします。ローディングの状態変化やそれ以上読み込み可能かどうかの判断など、実装パターンは大体何を使うのでも同じだと思うので、好きな書き方でよさそうです。

onEndReachedが呼ばれるタイミングはonEndReachedThresholdで変えることができ、リストの一番下からの距離を、リストの見える範囲の高さ(全体の高さではなく)を1として指定できます。デフォルトは0.5ですが今回は0を指定し、一番下まで完全にスクロールしてから読み込むようにしてみました。

getPostsアクションはこのようにしました。

redux.js(部分)
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)を指定して取得を開始します。
QueryorderBylimitstartAfterを使って整列、個数指定、位置指定を行っています。
サブコレクションに対するQueryDocumentReferenceにさらにcollectionメソッドを使用することで取得できます。
これでFirestoreでのページング処理ができました。

Github

サンプルソースはこちらです
https://github.com/mildsummer/expo-auth-practice
https://github.com/mildsummer/expo-auth-practice-firebase

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした