пагинация хранилища пожара flutter с использованием streambuilder

привет, я пытаюсь использовать нумерацию страниц в моем списке. и данные списка поступают из firebase. Я не уверен, как разбивать данные на страницы с помощью построителя потоков. теперь я делаю разбиение на страницы, вызывая getdocuments в функции didChangeDependencies (). но после добавления новых данных список не обновляется. если кто-то может помочь, то это будет здорово для меня. вот что я делаю сейчас ..

 didChangeDependencies() {
    super.didChangeDependencies();

   getProducts();

    _scrollController.addListener(() {
      double maxScroll = _scrollController.position.maxScrollExtent;
      double currentScroll = _scrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.20;
      if (maxScroll - currentScroll <= delta) {
        getProducts();
      }
    });
  }

  getProducts() async {
    if (!hasMore) {
      return;
    }
    if (isLoading) {
      return;
    }
    setState(() {
      isLoading = true;
    });
    QuerySnapshot querySnapshot;
    if (lastDocument == null) {
      querySnapshot = await firestore
          .collection('products')
          .limit(documentLimit)
          .orderBy('timestamp', descending: true)
          .getDocuments();
    } else {
      querySnapshot = await firestore
          .collection('products')
          .startAfterDocument(lastDocument)
          .limit(documentLimit)
          .orderBy('timestamp', descending: true)
          .getDocuments();
    }
    if (querySnapshot.documents.length < documentLimit) {
      hasMore = false;
    }
    if (querySnapshot.documents.isNotEmpty) {
      lastDocument =
          querySnapshot.documents[querySnapshot.documents.length - 1];
      products.addAll(querySnapshot.documents);
      setState(() {
        isLoading = false;
      });
    }
  }

person Sourav Das    schedule 05.12.2019    source источник


Ответы (3)


Попробуйте использовать код ниже:

class ProductList extends StatefulWidget {
  @override
  _ProductListState createState() => _ProductListState();
}

class _ProductListState extends State<ProductList> {
  StreamController<List<DocumentSnapshot>> _streamController =
  StreamController<List<DocumentSnapshot>>();
  List<DocumentSnapshot> _products = [];

  bool _isRequesting = false;
  bool _isFinish = false;

  void onChangeData(List<DocumentChange> documentChanges) {
    var isChange = false;
    documentChanges.forEach((productChange) {
      if (productChange.type == DocumentChangeType.removed) {
        _products.removeWhere((product) {
          return productChange.document.documentID == product.documentID;
        });
        isChange = true;
      } else {

        if (productChange.type == DocumentChangeType.modified) {
          int indexWhere = _products.indexWhere((product) {
            return productChange.document.documentID == product.documentID;
          });

          if (indexWhere >= 0) {
            _products[indexWhere] = productChange.document;
          }
          isChange = true;
        }
      }
    });

    if(isChange) {
      _streamController.add(_products);
    }
  }

  @override
  void initState() {
    Firestore.instance
        .collection('products')
        .snapshots()
        .listen((data) => onChangeData(data.documentChanges));

    requestNextPage();
    super.initState();
  }

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification scrollInfo) {
          if (scrollInfo.metrics.maxScrollExtent == scrollInfo.metrics.pixels) {
            requestNextPage();
          }
          return true;
        },
        child: StreamBuilder<List<DocumentSnapshot>>(
          stream: _streamController.stream,
          builder: (BuildContext context,
              AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
            if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                return new Text('Loading...');
              default:
                log("Items: " + snapshot.data.length.toString());
                return ListView.separated(
                  separatorBuilder: (context, index) => Divider(
                    color: Colors.black,
                  ),
                  itemCount: snapshot.data.length,
                  itemBuilder: (context, index) => Padding(
                    padding: const EdgeInsets.symmetric(vertical: 32),
                    child: new ListTile(
                      title: new Text(snapshot.data[index]['name']),
                      subtitle: new Text(snapshot.data[index]['description']),
                    ),
                  ),
                );
            }
          },
        ));
  }

  void requestNextPage() async {
    if (!_isRequesting && !_isFinish) {
      QuerySnapshot querySnapshot;
      _isRequesting = true;
      if (_products.isEmpty) {
        querySnapshot = await Firestore.instance
            .collection('products')
            .orderBy('index')
            .limit(5)
            .getDocuments();
      } else {
        querySnapshot = await Firestore.instance
            .collection('products')
            .orderBy('index')
            .startAfterDocument(_products[_products.length - 1])
            .limit(5)
            .getDocuments();
      }

      if (querySnapshot != null) {
        int oldSize = _products.length;
        _products.addAll(querySnapshot.documents);
        int newSize = _products.length;
        if (oldSize != newSize) {
          _streamController.add(_products);
        } else {
          _isFinish = true;
        }
      }
      _isRequesting = false;
    }
  }
}

JSON для товара:

{
  "index": 1,
  "name": "Pork",
  "description": "Thịt heo/lợn"
}

Пример ссылки на Github: https://github.com/simplesoft-duongdt3/flutter_firestore_paging < / а>

person duongdt3    schedule 10.12.2019
comment
Не могли бы вы предоставить код для разбивки на страницы с использованием потока снимков запросов .... не снимков списка документов - person Sourav Das; 10.12.2019
comment
@SouravDas Я добавил пример виджета для вас. Пожалуйста, проверь это. - person duongdt3; 11.12.2019
comment
мне нужна разбивка на страницы с обновлением в реальном времени ... но ваш код работает только для разбивки на страницы .. но обновление в реальном времени не работает - person Sourav Das; 19.12.2019
comment
Если вы хотите обновлять в реальном времени, получать новые элементы при загрузке дополнительных, удалите _isFinish переменную. Теперь это будет соответствовать вашей цели. - person duongdt3; 19.12.2019
comment
@SouravDas Я отредактировал свой пример кода для обновления в реальном времени, теперь он работает. - person duongdt3; 19.12.2019
comment
@SouravDas Я имею в виду обновление в реальном времени, когда добавляется новый продукт - ›можно загрузить больше. Если вы хотите обновлять продукт в режиме реального времени - ›прислушивайтесь к изменениям -› обновите элемент списка. - person duongdt3; 19.12.2019
comment
не могли бы вы предоставить решение моей проблемы - person Sourav Das; 19.12.2019
comment
Позвольте нам продолжить это обсуждение в чате. - person Sourav Das; 19.12.2019
comment
Я слежу за этим и получил свои результаты. Но разве это не дало бы изначально тысячи чтений моим коллекциям продуктов в состоянии инициализации? Если да, то разве не стоит прислушиваться к их изменениям в методе requestNextPage, когда мы их читаем? - person rohan koshti; 26.06.2020
comment
@rohankoshti Я считаю то, что вы говорите, правильно, это работает очень хорошо, но я также считаю, что объем чтения чрезмерен. Я просто протестировал его в приложении чата и просмотрел тысячи чтений, даже выполнив всего несколько тестов. - person Pablo Crucitta; 30.06.2020
comment
@PabloCrucitta - спасибо .. хотя я обновил его, чтобы он потреблял меньше операций чтения ... до .listen ((data) = ›onChangeData (data.documentChanges)); добавить .limit (something_pagination_you_have) ... чтобы уменьшить ваши чтения .. Это будет прослушивать только добавленные снимки, а не все ненужные снимки. - person rohan koshti; 01.07.2020
comment
Импортировать импорт 'dart: async'; для StreamController - person Krishna Shetty; 09.10.2020

@ sourav-das, пожалуйста, посмотрите этот код

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Firestore firestore = Firestore.instance;
  List<DocumentSnapshot> products = [];
  bool isLoading = false;
  bool hasMore = true;
  int documentLimit = 10;
  DocumentSnapshot lastDocument;
  ScrollController _scrollController = ScrollController();

  StreamController<List<DocumentSnapshot>> _controller =
      StreamController<List<DocumentSnapshot>>();

  Stream<List<DocumentSnapshot>> get _streamController => _controller.stream;

  @override
  void initState() {
    super.initState();
    getProducts();
    _scrollController.addListener(() {
      double maxScroll = _scrollController.position.maxScrollExtent;
      double currentScroll = _scrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.20;
      if (maxScroll - currentScroll <= delta) {
        getProducts();
      }
    });
  }

  getProducts() async {
    if (!hasMore) {
      print('No More Products');
      return;
    }
    if (isLoading) {
      return;
    }
    setState(() {
      isLoading = true;
    });
    QuerySnapshot querySnapshot;
    if (lastDocument == null) {
      querySnapshot = await firestore
          .collection('products')
          .orderBy('name')
          .limit(documentLimit)
          .getDocuments();
    } else {
      querySnapshot = await firestore
          .collection('products')
          .orderBy('name')
          .startAfterDocument(lastDocument)
          .limit(documentLimit)
          .getDocuments();
      print(1);
    }
    if (querySnapshot.documents.length < documentLimit) {
      hasMore = false;
    }

    lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];

    products.addAll(querySnapshot.documents);
    _controller.sink.add(products);

    setState(() {
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Pagination with Firestore'),
      ),
      body: Column(children: [
        Expanded(
          child: StreamBuilder<List<DocumentSnapshot>>(
            stream: _streamController,
            builder: (sContext, snapshot) {
              if (snapshot.hasData && snapshot.data.length > 0) {
                return ListView.builder(
                  controller: _scrollController,
                  itemCount: snapshot.data.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      contentPadding: EdgeInsets.all(5),
                      title: Text(snapshot.data[index].data['name']),
                      subtitle: Text(snapshot.data[index].data['short_desc']),
                    );
                  },
                );
              } else {
                return Center(
                  child: Text('No Data...'),
                );
              }
            },
          ),
        ),
        isLoading
            ? Container(
                width: MediaQuery.of(context).size.width,
                padding: EdgeInsets.all(5),
                color: Colors.yellowAccent,
                child: Text(
                  'Loading',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
              )
            : Container()
      ]),
    );
  }
}
person Maulik Sinroja    schedule 10.12.2019
comment
Вы делаете то же самое, что и я ... Я спрашиваю, как мне использовать streambuilder для разбивки на страницы. Так что я могу слушать изменения данных .. - person Sourav Das; 10.12.2019
comment
@SouravDas Пожалуйста, убедитесь, что я редактировал код с помощью StreamBuilder. - person Maulik Sinroja; 10.12.2019
comment
когда я прокручиваю, он удаляет предыдущий элемент списка, заменяет его новым элементом. например, если всего 20 элементов, и я добавил ограничение в 10, то после прокрутки первых 10 он удаляет их и добавляет новые 10 элементов ... это не правильный путь - person Sourav Das; 10.12.2019
comment
Вы присваиваете значение переменной products, но где вы его использовали ... streambuilder такой же, как и раньше ... пожалуйста, предоставьте правильное решение, если можете. Спасибо - person Sourav Das; 10.12.2019
comment
Для меня он отлично работает в моем коде, пожалуйста, проверьте свой код построчно. Я думаю, вы сделали что-то не так в своем коде. - person Maulik Sinroja; 10.12.2019
comment
Давайте продолжим это обсуждение в чате. - person Sourav Das; 10.12.2019
comment
@MaulikGajjar: Кто-нибудь из вас понял эту проблему? Приведенный выше код работает для меня, но я хочу немедленно обновить его после добавления нового документа (в случае приложения чата), а этот код этого не делает. - person PJQuakJag; 12.02.2020

Я сделал нечто подобное с чатами вместо продуктов.

Посмотрите, полезен ли этот код:

class _MessagesState extends State<Messages> {
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      if (_scrollController.offset >=
              (_scrollController.position.maxScrollExtent) &&
          !_scrollController.position.outOfRange) {
        _getChats();
      }
    });
  }

  final StreamController<List<DocumentSnapshot>> _chatController =
      StreamController<List<DocumentSnapshot>>.broadcast();

  List<List<DocumentSnapshot>> _allPagedResults = [<DocumentSnapshot>[]];

  static const int chatLimit = 10;
  DocumentSnapshot? _lastDocument;
  bool _hasMoreData = true;

  Stream<List<DocumentSnapshot>> listenToChatsRealTime() {
    _getChats();
    return _chatController.stream;
  }

  void _getChats() {
    final CollectionReference _chatCollectionReference = FirebaseFirestore
        .instance
        .collection("ChatRoom")
        .doc(widget.chatRoomId)
        .collection("channel");
    var pagechatQuery = _chatCollectionReference
        .orderBy('createdAt', descending: true)
        .limit(chatLimit);

    if (_lastDocument != null) {
      pagechatQuery = pagechatQuery.startAfterDocument(_lastDocument!);
    }

    if (!_hasMoreData) return;

    var currentRequestIndex = _allPagedResults.length;
    pagechatQuery.snapshots().listen(
      (snapshot) {
        if (snapshot.docs.isNotEmpty) {
          var generalChats = snapshot.docs.toList();

          var pageExists = currentRequestIndex < _allPagedResults.length;

          if (pageExists) {
            _allPagedResults[currentRequestIndex] = generalChats;
          } else {
            _allPagedResults.add(generalChats);
          }

          var allChats = _allPagedResults.fold<List<DocumentSnapshot>>(
              <DocumentSnapshot>[],
              (initialValue, pageItems) => initialValue..addAll(pageItems));

          _chatController.add(allChats);

          if (currentRequestIndex == _allPagedResults.length - 1) {
            _lastDocument = snapshot.docs.last;
          }

          _hasMoreData = generalChats.length == chatLimit;
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: StreamBuilder<List<DocumentSnapshot>>(
          stream: listenToChatsRealTime(),
          builder: (ctx, chatSnapshot) {
            if (chatSnapshot.connectionState == ConnectionState.waiting ||
                chatSnapshot.connectionState == ConnectionState.none) {
              return chatSnapshot.hasData
                  ? Center(
                      child: CircularProgressIndicator(),
                    )
                  : Center(
                      child: Text("Start a Conversation."),
                    );
            } else {
              if (chatSnapshot.hasData) {
                final chatDocs = chatSnapshot.data!;
                final user = Provider.of<User?>(context);
                return ListView.builder(
                  controller: _scrollController,
                  reverse: true,
                  itemBuilder: (ctx, i) {
                    Map chatData = chatDocs[i].data() as Map;
                    return MessageBubble(
                        username: chatData['username'],
                        message: chatData['text'],
                        isMe: chatData['senderId'] == user!.uid,
                        key: ValueKey(chatDocs[i].id));
                  },
                  itemCount: chatDocs.length,
                );
              } else {
                return CircularProgressIndicator();
              }
            }
          }),
    );
  }
}

Я сослался на этот ответ: Разбивка на страницы во Flutter с базой данных Firebase Realtime

person Agrim    schedule 29.06.2021