StreamBuilder Firestore Pagination - PullRequest
       9

StreamBuilder Firestore Pagination

0 голосов
/ 03 января 2019

Я новичок, чтобы трепетать, и я пытаюсь разбить чат на страницы, когда прокрутка достигает вершины с помощью streambuilder. Проблема заключается в следующем: когда я делаю запрос в scrollListener streambuilder, назначаю приоритет его запросу над scrollListener и возвращает старый ответ. Есть какой-либо способ сделать это? Какие у меня есть варианты? Спасибо!

Класс ChatScreenState

В initState я создаю прослушиватель прокрутки.

  @override
void initState() {
 listScrollController = ScrollController();
 listScrollController.addListener(_scrollListener);
 super.initState();
}

Здесь я создаю StreamBuilder с запросом, ограниченным 20 последними сообщениями. Использование _messagesSnapshots в качестве глобального списка.

@override
Widget build(BuildContext context) {
 return Scaffold(
    key: key,
    appBar: AppBar(title: Text("Chat")),
    body: Container(
      child: Column(
        children: <Widget>[
          Flexible(
              child: StreamBuilder<QuerySnapshot>(
            stream: Firestore.instance
                .collection('messages')
                .where('room_id', isEqualTo: _roomID)
                .orderBy('timestamp', descending: true)
                .limit(20)
                .snapshots(),
            builder: (context, snapshot) {
              if (!snapshot.hasData) return LinearProgressIndicator();
              _messagesSnapshots = snapshot.data.documents;

              return _buildList(context, _messagesSnapshots);
            },
          )),
          Divider(height: 1.0),
          Container(
            decoration: BoxDecoration(color: Theme.of(context).cardColor),
            child: _buildTextComposer(),
          ),
        ],
      ),
    ));
}

Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
 _messagesSnapshots = snapshot;

 return ListView.builder(
   controller: listScrollController,
   itemCount: _messagesSnapshots.length,
   reverse: true,
   itemBuilder: (context, index) {
     return _buildListItem(context, _messagesSnapshots[index]);
   },
 );
}

И в методе _scollListener я запрашиваю следующие 20 сообщений и добавляю результат в глобальный список.

  _scrollListener() {

   // If reach top 
   if (listScrollController.offset >=
        listScrollController.position.maxScrollExtent &&
    !listScrollController.position.outOfRange) {

   // Then search last message
   final message = Message.fromSnapshot(
      _messagesSnapshots[_messagesSnapshots.length - 1]);

   // And get the next 20 messages from database
   Firestore.instance
      .collection('messages')
      .where('room_id', isEqualTo: _roomID)
      .where('timestamp', isLessThan: message.timestamp)
      .orderBy('timestamp', descending: true)
      .limit(20)
      .getDocuments()
      .then((snapshot) {

    // To save in the global list
    setState(() {
      _messagesSnapshots.addAll(snapshot.documents);
    });
  });

  // debug snackbar
  key.currentState.showSnackBar(new SnackBar(
    content: new Text("Top Reached"),
  ));
 }
}

Ответы [ 2 ]

0 голосов
/ 27 марта 2019

Я опубликую свой код, я надеюсь, что кто-то опубликует лучшее решение, возможно, не лучшее, но оно работает.

В моем приложении реальное решение - изменить состояние списка при достижении вершины., остановить поток и показать старые сообщения.

Весь код (состояние)

class _MessageListState extends State<MessageList> {
  List<DocumentSnapshot> _messagesSnapshots;
  bool _isLoading = false;

  final TextEditingController _textController = TextEditingController();
  ScrollController listScrollController;
  Message lastMessage;
  Room room;

  @override
  void initState() {
    listScrollController = ScrollController();
    listScrollController.addListener(_scrollListener);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    room = widget.room;
    return Flexible(
      child: StreamBuilder<QuerySnapshot>(
        stream: _isLoading
            ? null
            : Firestore.instance
                .collection('rooms')
                .document(room.id)
                .collection('messages')
                .orderBy('timestamp', descending: true)
                .limit(20)
                .snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) return LinearProgressIndicator();
          _messagesSnapshots = snapshot.data.documents;
          return _buildList(context, _messagesSnapshots);
        },
      ),
    );
  }

  Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
    _messagesSnapshots = snapshot;

    if (snapshot.isNotEmpty) lastMessage = Message.fromSnapshot(snapshot[0]);

    return ListView.builder(
      padding: EdgeInsets.all(10),
      controller: listScrollController,
      itemCount: _messagesSnapshots.length,
      reverse: true,
      itemBuilder: (context, index) {
        return _buildListItem(context, _messagesSnapshots[index]);
      },
    );
  }

  Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
    final message = Message.fromSnapshot(data);
    Widget chatMessage = message.sender != widget.me.id
        ? Bubble(
            message: message,
            isMe: false,
          )
        : Bubble(
            message: message,
            isMe: true,
          );
    return Column(
      children: <Widget>[chatMessage],
    );
  }

  loadToTrue() {
    _isLoading = true;
    Firestore.instance
        .collection('messages')
        .reference()
        .where('room_id', isEqualTo: widget.room.id)
        .orderBy('timestamp', descending: true)
        .limit(1)
        .snapshots()
        .listen((onData) {
      print("Something change");
      if (onData.documents[0] != null) {
        Message result = Message.fromSnapshot(onData.documents[0]);
        // Here i check if last array message is the last of the FireStore DB
        int equal = lastMessage?.compareTo(result) ?? 1;
        if (equal != 0) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    });
  }

  _scrollListener() {
    // if _scroll reach top 
    if (listScrollController.offset >=
            listScrollController.position.maxScrollExtent &&
        !listScrollController.position.outOfRange) {
      final message = Message.fromSnapshot(
          _messagesSnapshots[_messagesSnapshots.length - 1]);
      // Query old messages
      Firestore.instance
          .collection('rooms')
          .document(widget.room.id)
          .collection('messages')
          .where('timestamp', isLessThan: message.timestamp)
          .orderBy('timestamp', descending: true)
          .limit(20)
          .getDocuments()
          .then((snapshot) {
        setState(() {
          loadToTrue();
          // And add to the list
          _messagesSnapshots.addAll(snapshot.documents);
        });
      });
      // For debug purposes
//      key.currentState.showSnackBar(new SnackBar(
//        content: new Text("Top reached"),
//      ));
    }
  }
}

Наиболее важные методы:

_scrollListener

Когда я достигаю вершины, я запрашиваю старые сообщения и в setState я устанавливаю isLoading var в true и устанавливаю со старыми сообщениями массив, который я собираюсь показать.

  _scrollListener() {
    // if _scroll reach top
    if (listScrollController.offset >=
            listScrollController.position.maxScrollExtent &&
        !listScrollController.position.outOfRange) {
      final message = Message.fromSnapshot(
          _messagesSnapshots[_messagesSnapshots.length - 1]);
      // Query old messages
      Firestore.instance
          .collection('rooms')
          .document(widget.room.id)
          .collection('messages')
          .where('timestamp', isLessThan: message.timestamp)
          .orderBy('timestamp', descending: true)
          .limit(20)
          .getDocuments()
          .then((snapshot) {
        setState(() {
          loadToTrue();
          // And add to the list
          _messagesSnapshots.addAll(snapshot.documents);
        });
      });
      // For debug purposes
//      key.currentState.showSnackBar(new SnackBar(
//        content: new Text("Top reached"),
//      ));
    }
  }

И loadToTrue, которые слушают, пока мы ищем старые сообщения.Если появляется новое сообщение, мы повторно активируем поток.

loadToTrue

  loadToTrue() {
    _isLoading = true;
    Firestore.instance
        .collection('rooms')
        .document(widget.room.id)
        .collection('messages')
        .orderBy('timestamp', descending: true)
        .limit(1)
        .snapshots()
        .listen((onData) {
      print("Something change");
      if (onData.documents[0] != null) {
        Message result = Message.fromSnapshot(onData.documents[0]);
        // Here i check if last array message is the last of the FireStore DB
        int equal = lastMessage?.compareTo(result) ?? 1;
        if (equal != 0) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    });
  }

Надеюсь, это поможет всем, у кого возникла такая же проблема (@Purus), и подождатьпока кто-нибудь не даст нам лучшее решение!

0 голосов
/ 04 января 2019

Прежде всего, я сомневаюсь, что такой API является правильным бэкендом для приложения чата с живыми данными - API с разбивкой по страницам лучше подходят для статического контента . Например, что именно означает «страница 2», если после загрузки «страницы 1» было добавлено 30 сообщений? Также обратите внимание, что Firebase взимает плату за запросы Firestore для каждого документа, поэтому каждое запрашиваемое сообщение дважды наносит ущерб вашей квоте и вашему кошельку .

Как видите, разбитый на страницы API с фиксированной длиной страницы, вероятно, не подходит. Поэтому я настоятельно рекомендую вам запрашивать сообщения, отправленные за определенный промежуток времени. Запрос Firestore может содержать такой код:

.where("time", ">", lastCheck).where("time", "<=", DateTime.now())

В любом случае, вот мой ответ на аналогичный вопрос о разбитых на страницы API во Flutter , который содержит код для фактической реализации, которая загружает новый контент как ListView прокрутки.

...