🔥Webサービス開発ログ Part 3 (Infinite Loading with Firebase and GraphQL)

はじめに

本エントリはWebアプリの開発記録です。GraphQLを利用したサンプルアプリを作る過程を記録していきます。
ソースコードはGitHubにpushしています。

開発中のアプリはこちらのリンクでご覧ください。

Realtime DatabaseとApollo Clientの接続

Realtime Databaseに対してGraphQLを利用して問い合わせをしたい。

そのため、apollo-link-firebaseを使います。

現時点ではFirebaseはGraphQLに対応していないので、Apollo Clientを利用するためにはこうしたライブラリが必要となります。

$ yarn add apollo-link-firebase

FirebaseとApollo Clientの接続部分をindex.jsに書きます。

if (!firebase.apps.length) {
  firebase.initializeApp({
      ...config
  });
}

const rtdbLink = createRtdbLink({ database: firebase.database() });
const client = new ApolloClient({
  link: rtdbLink,
  cache: new InMemoryCache(),
});

const App = () => (
  <ApolloProvider client={client}>
    <NewsList />
  </ApolloProvider>
);

RSSデータの作成

テストデータを作るため、RSSを取得する対象のサイトをマスタに登録します。

ひとまず更新が活発で質が高いと思われる下記のブログを選んで登録させていただきました。

マスタはこんな感じです。

img

Firebase Functions Shellを利用してfetchRSS()を実行し、記事のデータを登録します。

ひとまず、今週の週初め(日曜)を基準に50日前の記事まで登録しました。

img

無限ローディング(Infinte Loading)の実装

次に、RSSを表示するためのページを作成します。

ニュースの一覧を取得するためのGraphQLを定義します。

@rtdbQueryにFirebase RealtimeDatabaseのテーブル名などを書きます。
Infinite Loadingを実装したいので、LimitToFirstとstartAtを定義します。

import gql from 'graphql-tag';

const NEWS_LIST_QUERY = gql<code>query($startAt: String) {
    newsList
      @rtdbQuery(
        ref: "newsList"
        type: "NewsList"
        orderByKey: true
        startAt: $startAt
        limitToFirst: 10
      )
      @array {
      id @key
      baseDate
      link
      name
      pubDate
      summary
      title
      sortKey
    }
  }</code>;

関数型コンポーネントをとして実装します。全体は次のようになりました。

めっちゃシンプル😆

Reactはコンポーネントをうまく使えると本当に気持ちよくコードがかけるフレームワークだと思います。

import React, { Component } from 'react';
import { View, Text } from 'react-native-web';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import _ from 'lodash';
import NewsList from '../screens/NewsList';

const NEWS_LIST_QUERY = gql<code>query($startAt: String) {
    newsList
      @rtdbQuery(
        ref: "newsList"
        type: "NewsList"
        orderByChild: "sortKey"
        startAt: $startAt
        limitToFirst: 10
      )
      @array {
      id @key
      baseDate
      link
      name
      pubDate
      summary
      title
      sortKey
    }
  }</code>;

export default () => (
  <View>
    <Query
      query={NEWS_LIST_QUERY}
      variables={{ startAt: Number.MIN_SAFE_INTEGER }}
      fetchPolicy="cache-and-network"
    >
      {({ data, fetchMore }) => {
        const { newsList } = data;
        if (newsList === undefined) {
          return <Text>Loading...</Text>;
        }
        if (newsList.length === 0) {
          return <Text>Error!</Text>;
        }
        return (
          <NewsList
            newsList={newsList}
            onEndReached={() =>
              fetchMore({
                variables: { startAt: _.last(newsList).sortKey + 1 },
                updateQuery: (prev, { fetchMoreResult }) => {
                  if (
                    !fetchMoreResult ||
                    fetchMoreResult.newsList.length === 0
                  ) {
                    return prev;
                  }
                  if (
                    _.last(fetchMoreResult.newsList).id ===
                    _.last(prev.newsList).id
                  ) {
                    // avoid duplication
                    return prev;
                  }
                  return Object.assign({}, prev, {
                    newsList: [...prev.newsList, ...fetchMoreResult.newsList],
                  });
                },
              })
            }
          />
        );
      }}
    </Query>
  </View>
);

問題発生と対処(apollo-link-firebaseのバグ)

クエリにstartAtとlimitToFirstを指定するとstartAtが効かないという現象が発生。

なぜ?と思い、GraphQLの書き方を変えたりドキュメントを見直しても一向に直らなかったため、apollo-link-firebaseのソースコードを調べたところ、バグを発見。

startAtとlimitToFirstを同時に指定するとlimitToFirstしか効かないという問題があることがわかりました。

これについては、修正Pull Requestを送り、即日マージされました。

Infinite Loadingの実装完了

スクロールすると、追加の古い記事が読み込まれます。
iPhoneで見ると、なかなか良い動作をしていると思います。

img

News App

Please follow and like us:

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です