14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter x Firestore x 無限スクロール

Posted at

#はじめに
表題通り、FlutterとFirestoreで無限スクロールを作った覚書です。
アプリでリストを実装するときには、かなり利用する機会の多い実装かなと思いますが、
意外にハマることもあるので、何かの参考になればと思います。

#RefreshIndicatorの実装

オーバースクロールで画面を更新する、または任意の処理を実行する機能は、FlutterのRefreshIndicatorというウィジェットで定義されているため、まずはこのIndicatorがベースになります。
https://api.flutter.dev/flutter/material/RefreshIndicator-class.html

###やること1.RefreshIndicatorでListViewなどのスクローラブルな要素を包む

###やること2.onRefreshを定義する

main.dart

      body: RefreshIndicator(
        child: ListView.builder(
          controller: controller,
          itemCount: _data.length + 1,
          itemBuilder: (_, int index) {
            if (index < _data.length) {
              final DocumentSnapshot document = _data[index];
              return new Container(
                height: 200.0,
                child: new Text(document['title']),
              );
            }
          },
        ),
        onRefresh: () async {
          _data.clear();
          _lastVisible = null;
          await _getData();
        },
      )

#ScrollListenrの実装

スクロールされた箇所を検知して、データを読み込むようにします。

position.maxScrollExtent => ListView全体の下端位置
position.pixels => 現在の表示位置

main.dart

  @override
  void initState() {
    controller = new ScrollController()..addListener(_scrollListener);
  }

  void _scrollListener() {
    if (!_isLoading) {
      if (controller.position.pixels == controller.position.maxScrollExtent) {
        setState(() => _isLoading = true);
        _getData();
      }
    }
  }

#Firebaseでデータを取得する

Firestoreでデータを取得します。orderByされたリストをstartAfterを使って、区切って
スクロールされるごとに少しずつリストが読み込まれるようにします。

main.dart

    if (_lastVisible == null)
      data = await Firestore.instance
          .collection('projects')
          .orderBy('start_at', descending: true)
          .limit(4)
          .getDocuments();
    else
      data = await Firestore.instance
          .collection('projects')
          .orderBy('start_at', descending: true)
          .startAfter([_lastData['start_at']])
          .limit(4)
          .getDocuments();

    if (data != null && data.documents.length > 0) {
          _lastData = data.documents[data.documents.length - 1];
    }

#全体のコード

はこんな感じです。あくまで参考としてです。

main.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class Project extends StatefulWidget {
  Project({this.firestore});

  final Firestore firestore;

  @override
  _ProjectState createState() => _ProjectState();
}

class _ProjectState extends State<Project> {
  ScrollController controller;
  DocumentSnapshot _lastData;
  bool _isLoading;

  List<DocumentSnapshot> _data = new List<DocumentSnapshot>();
  final scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {
    controller = new ScrollController()..addListener(_scrollListener);
    super.initState();
    _isLoading = true;
    _getData();
  }

  Future<Null> _getData() async {
    QuerySnapshot data;
    if (_lastData == null)
      data = await Firestore.instance
          .collection('projects')
          .orderBy('start_at', descending: true)
          .limit(4)
          .getDocuments();
    else
      data = await Firestore.instance
          .collection('projects')
          .orderBy('start_at', descending: true)
          .startAfter([_lastData['start_at']])
          .limit(4)
          .getDocuments();

    if (data != null && data.documents.length > 0) {
      _lastData = data.documents[data.documents.length - 1];
      if (mounted) {
        setState(() {
          _isLoading = false;
          _data.addAll(data.documents);
        });
      }
    } else {
      setState(() => _isLoading = false);
      scaffoldKey.currentState?.showSnackBar(
        SnackBar(
          content: Text('データがありません.'),
        ),
      );
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      body: RefreshIndicator(
        child: ListView.builder(
          controller: controller,
          itemCount: _data.length + 1,
          itemBuilder: (_, int index) {
            if (index < _data.length) {
              final DocumentSnapshot document = _data[index];
              return new Container(
                height: 200.0,
                child: new Text(document['title']),
              );
            }
          },
        ),
        onRefresh: () async {
          _data.clear();
          _lastData = null;
          await _getData();
        },
      ),
    );
  }

  void _scrollListener() {
    if (!_isLoading) {
      if (controller.position.pixels == controller.position.maxScrollExtent) {
        setState(() => _isLoading = true);
        _getData();
      }
    }
  }
}
14
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?