Stream Builder 加载更多数据

Stream Builder load more data

我试图在到达流生成器的末尾后加载更多数据,每次到达末尾时,流生成器都会重新加载并弹回到开头,它不允许我向下滚动(保持重新加载并弹回顶部)。

我尝试的是一旦我到达终点,增加 firebase 的限制,但我不确定问题是什么..任何人都可以帮助我吗?

这是我想要做的事情的示例代码

  _onEndScroll(ScrollMetrics metrics) {
    //loadToTrue();
    setState(() {
      documentLimit = documentLimit + 10;
    });
  }



 StreamBuilder(
              initialData: cache,
              stream: FirebaseFirestore.instance
                      .collection("timeline")
                      .doc(widget.currentUser.id)
                      .collection('timelinePosts')
                      .orderBy('timestamp', descending: true)
                      .limit(documentLimit)
                      .snapshots(),
              builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> streamSnapshot) {
                items = streamSnapshot.data != null &&
                        streamSnapshot.data.docs != null
                    ? streamSnapshot.data.docs
                    : [];

                List<Post> posts =
                    items.map((doc) => Post.fromDocument(doc)).toList();


                cache = streamSnapshot.data;

                return !streamSnapshot.hasData ||
                        streamSnapshot.connectionState ==
                            ConnectionState.waiting
                    ? Center(
                        child: CircularProgressIndicator(),
                      )
                    : NotificationListener<ScrollNotification>(
                        onNotification: (scrollNotification) {
                          if (scrollNotification is ScrollEndNotification) {
                            _onEndScroll(scrollNotification.metrics);
                          }
                        },
                        child: Container(
                            height: MediaQuery.of(context).size.height - 200,
                            margin: EdgeInsets.only(bottom: 1),
                            child: ListView.builder(
                                physics: BouncingScrollPhysics(
                                    parent: ScrollPhysics()),
                                shrinkWrap: true,
                                itemCount: items.length,
                                itemBuilder: (_, i) =>
                                    timelineDecision == "follow"
                                        ? posts[i]
                                        : postsLocal[i])));
              })

这是所有人的代码

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

import 'package:fluttershare/pages/home.dart';
import 'package:fluttershare/models/user.dart';
import 'package:fluttershare/pages/search.dart';
import 'package:fluttershare/pages/upload.dart';
import 'package:fluttershare/pages/upload_limit.dart';
import 'package:fluttershare/widgets/post.dart';

import 'package:latlong/latlong.dart';

final usersRef = FirebaseFirestore.instance.collection('users');

class Timeline extends StatefulWidget {
  final User currentUser;

  Timeline({this.currentUser});
  @override
  _TimelineState createState() => _TimelineState();
}

class _TimelineState extends State<Timeline> {
  QuerySnapshot cache;
  List<Post> posts;
  List<Post> postsLocal;
  List<DocumentSnapshot> items;
  List<String> followingList = [];
  String timelineDecision = "local";

  String address = "Norman";

  ScrollController listScrollController;
  int documentLimit = 5;
  void initState() {
    super.initState();
    getFollowing();
  }

  getFollowing() async {
    QuerySnapshot snapshot = await followingRef
        .doc(currentUser.id)
        .collection('userFollowing')
        .get();
    if (mounted) {
      setState(() {
        followingList = snapshot.docs.map((doc) => doc.id).toList();
      });
    }
  }

  setTimeline(String timelineDecision) {
    setState(() {
      this.timelineDecision = timelineDecision;
    });
  }

  buildToggleTimeline() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        TextButton(
          onPressed: () => setTimeline("follow"),
          child: Text('Following',
              style: TextStyle(
                  color: timelineDecision == 'follow'
                      ? Theme.of(context).primaryColor
                      : Colors.grey)),
          style: TextButton.styleFrom(
            textStyle: const TextStyle(fontSize: 20),
          ),
        ),
        TextButton(
            onPressed: () => setTimeline("local"),
            child: Text('Local',
                style: TextStyle(
                    color: timelineDecision == 'local'
                        ? Theme.of(context).primaryColor
                        : Colors.grey)),
            style: TextButton.styleFrom(
              textStyle: const TextStyle(fontSize: 20),
            )),
      ],
    );
  }

  _onEndScroll(ScrollMetrics metrics) {
    //loadToTrue();
    setState(() {
      documentLimit = documentLimit + 10;
    });
  }

  @override
  Widget build(context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('Entango'),
        actions: <Widget>[
          PopupMenuButton(
              icon: const Icon(Icons.add),
              onSelected: choiceAction,
              itemBuilder: (context) => [
                    PopupMenuItem(
                      child: Text("Timelimit post"),
                      value: 1,
                    ),
                    PopupMenuItem(
                      child: Text("Normal post"),
                      value: 2,
                    )
                  ]),
          IconButton(
            icon: const Icon(Icons.search),
            tooltip: 'Show Snackbar',
            onPressed: () => Navigator.push(
                context, MaterialPageRoute(builder: (context) => Search())),
          )
        ],
      ),
      body: ListView(
        children: <Widget>[
          buildToggleTimeline(),
          Divider(
            height: 0.0,
          ),
          //buildSlider(),

          StreamBuilder(
              initialData: cache,
              stream: timelineDecision == "follow"
                  ? FirebaseFirestore.instance
                      .collection("timeline")
                      .doc(widget.currentUser.id)
                      .collection('timelinePosts')
                      .orderBy('timestamp', descending: true)
                      .limit(documentLimit)
                      .snapshots()
                  : FirebaseFirestore.instance
                      .collection("postsLocalRef")
                      .doc(address)
                      .collection("userPosts")
                      .orderBy('timestamp', descending: true)
                      .limit(documentLimit)
                      .snapshots(),
              builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> streamSnapshot) {
                items = streamSnapshot.data != null &&
                        streamSnapshot.data.docs != null
                    ? streamSnapshot.data.docs
                    : [];

                List<Post> posts =
                    items.map((doc) => Post.fromDocument(doc)).toList();

                List<Post> postsLocal =
                    items.map((doc) => Post.fromDocument(doc)).toList();

                cache = streamSnapshot.data;

                return !streamSnapshot.hasData ||
                        streamSnapshot.connectionState ==
                            ConnectionState.waiting
                    ? Center(
                        child: CircularProgressIndicator(),
                      )
                    : NotificationListener<ScrollNotification>(
                        onNotification: (scrollNotification) {
                          if (scrollNotification is ScrollEndNotification) {
                            _onEndScroll(scrollNotification.metrics);
                          }
                        },
                        child: Container(
                            height: MediaQuery.of(context).size.height - 200,
                            margin: EdgeInsets.only(bottom: 1),
                            child: ListView.builder(
                                physics: BouncingScrollPhysics(
                                    parent: ScrollPhysics()),
                                shrinkWrap: true,
                                itemCount: items.length,
                                itemBuilder: (_, i) =>
                                    timelineDecision == "follow"
                                        ? posts[i]
                                        : postsLocal[i])));
              })
        ],
      ),
    );
  }

  void choiceAction(int value) {
    if (value == 1) {
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => Upload_limit(
                    currentUser: currentUser,
                  )));
    } else {
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => Upload(
                    currentUser: currentUser,
                  )));
    }
  }
}

我能找到的问题很少。其中之一是使用 StreamBuilder 来获取分页数据。虽然它在理论上可能很棒,但它在 Firebase 的情况下不起作用,因为 Firebase 正在提供 Stream。所以每次调用 setState 时,都会创建一个新的 steam。我已经编写了用于以分页方式从 firestore 获取数据的示例应用程序。

//main.dart
import 'dart:async';

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;
  const MyHomePage({Key? key, required this.title}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  static const PAGE_SIZE = 30;

  bool _allFetched = false;
  bool _isLoading = false;
  List<ColorDetails> _data = [];
  DocumentSnapshot? _lastDocument;

  @override
  void initState() {
    super.initState();
    _fetchFirebaseData();
  }

  Future<void> _fetchFirebaseData() async {
    if (_isLoading) {
      return;
    }
    setState(() {
      _isLoading = true;
    });
    Query _query = FirebaseFirestore.instance
        .collection("sample_data")
        .orderBy('color_label');
    if (_lastDocument != null) {
      _query = _query.startAfterDocument(_lastDocument!).limit(PAGE_SIZE);
    } else {
      _query = _query.limit(PAGE_SIZE);
    }

    final List<ColorDetails> pagedData = await _query.get().then((value) {
      if (value.docs.isNotEmpty) {
        _lastDocument = value.docs.last;
      } else {
        _lastDocument = null;
      }
      return value.docs
          .map((e) => ColorDetails.fromMap(e.data() as Map<String, dynamic>))
          .toList();
    });

    setState(() {
      _data.addAll(pagedData);
      if (pagedData.length < PAGE_SIZE) {
        _allFetched = true;
      }
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: NotificationListener<ScrollEndNotification>(
        child: ListView.builder(
          itemBuilder: (context, index) {
            if (index == _data.length) {
              return Container(
                key: ValueKey('Loader'),
                width: double.infinity,
                height: 60,
                child: Center(
                  child: CircularProgressIndicator(),
                ),
              );
            }
            final item = _data[index];
            return ListTile(
              key: ValueKey(
                item,
              ),
              tileColor: Color(item.code | 0xFF000000),
              title: Text(
                item.label,
                style: TextStyle(color: Colors.white),
              ),
            );
          },
          itemCount: _data.length + (_allFetched ? 0 : 1),
        ),
        onNotification: (scrollEnd) {
          if (scrollEnd.metrics.atEdge && scrollEnd.metrics.pixels > 0) {
            _fetchFirebaseData();
          }
          return true;
        },
      ),
    );
  }
}

class ColorDetails {
  final String label;
  final int code;
  ColorDetails(this.code, this.label);

  factory ColorDetails.fromMap(Map<String, dynamic> json) {
    return ColorDetails(json['color_code'], json['color_label']);
  }

  Map toJson() {
    return {
      'color_code': code,
      'color_label': label,
    };
  }
}

有兴趣的可以去https://blog.litedevs.com/infinite-scroll-list-using-flutter-firebase-firestore

看看我写的文章