Je suis nouveau dans le flutter, et j'essaie de paginer une discussion lorsque le défilement atteint le haut avec streambuilder. Le problème est le suivant: lorsque je fais la requête dans scrollListener streambuilder, priorise sa requête au-dessus de scrollListener et renvoie l'ancienne réponse. Est-ce qu'il y a un moyen de faire ça? Quelles sont mes options ici? Merci!
Classe ChatScreenState
Dans initState, je crée l'écouteur de défilement.
_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"), )); } }
Ici, je crée le StreamBuilder avec la requête limitée à 20 derniers messages. Utilisation des _messagesSnapshots comme liste globale.
@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]); }, ); }
Et dans la méthode _scollListener, j'interroge les 20 messages suivants et j'ajoute le résultat à la liste globale.
@override void initState() { listScrollController = ScrollController(); listScrollController.addListener(_scrollListener); super.initState(); }
3 Réponses :
Tout d'abord, je doute qu'une telle API soit le bon backend pour une application de chat avec des données en direct - Les API paginées sont mieux adaptées au contenu statique . Par exemple, à quoi fait exactement référence «page 2» si 30 messages ont été ajoutés après le chargement de «page 1»? Notez également que Firebase facture les demandes Firestore sur une base par document, de sorte que chaque message qui est demandé deux fois nuit à votre quota et à votre portefeuille .
Comme vous le voyez, une API paginée avec un la longueur de la page n'est probablement pas la bonne. C'est pourquoi je vous conseille fortement de plutôt demander des messages qui ont été envoyés dans un certain intervalle de temps. La requête Firestore peut contenir du code comme celui-ci:
.where("time", ">", lastCheck).where("time", "<=", DateTime.now())
De toute façon, voici ma réponse à une question similaire sur les API paginées dans Flutter , qui contient le code d'une implémentation réelle qui charge le nouveau contenu sous forme de ListView
défile.
En fait, je pense que Firestore ne facture pas les demandes de documents en double. Ils facturent 1 document lu par requête + chaque nouveau document récupéré + chaque mise à jour de document (le contenu a changé). Ils ont une limite de session + une limite de temps où ils se rechargent pour la même demande de document.
Je vais publier mon code, j'espère que quelqu'un publiera une meilleure solution, ce n'est probablement pas la meilleure mais cela fonctionne.
Dans mon application, la solution actuelle est de changer l'état de la liste lorsque vous atteignez le sommet, arrêtez le flux et afficher les anciens messages.
Tout le code (État)
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; }); } } }); }
Les méthodes les plus importantes sont:
_scrollListenerLorsque j'atteins le haut, j'interroge d'anciens messages et dans setState, je règle isLoading var sur true et avec les anciens messages, le tableau im va montrer.
_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"), // )); } }
Et loadToTrue qui écoutent pendant que nous recherchons d'anciens messages. S'il y a un nouveau message, nous réactivons le flux.
loadToTrue
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"), // )); } } }
J'espère que cela aidera tous ceux qui ont le même problème (@Purus) et attendez que quelqu'un nous donne une meilleure solution!
Excellente réponse, je voudrais ajouter une meilleure pratique. Vous devez annuler toutes les instances précédentes du flux "loadToTrue". Vous pouvez le faire comme ceci: Déclarez un champ, par exemple StreamSubscription
puis enregistrez l'instance du flux dans ce fichier, et dans _scrollListener faites // Annulez l'instance précédente d'abonnement au flux loadToTrue. if (onChangeSubscription! = null) {onChangeSubscription.cancel (); } // Démarre une nouvelle instance d'abonnement au flux loadToTrue. loadToTrue ();
J'ai un moyen de l'archiver. Désolé pour mon mauvais anglais
bool loadMoreMessage = false; int lastMessageIndex = 25 /// supposé à chaque fois faire défiler vers le haut de ListView charger plus de 25 documents Quand je défile vers le haut de ListView => setState loadMoreMessage = true;
Voici mon code: p>
StreamBuilder<List<Message>>( stream: _loadMoreMessage ? _streamMessage(lastMessageIndex): _streamMessage(25), builder: (context, AsyncSnapshot<List<Message>> snapshot) { if (!snapshot.hasData) { return Container(); } else { listMessage = snapshot.data; return NotificationListener( onNotification: (notification) { if (notification is ScrollEndNotification) { if (notification.metrics.pixels > 0) { setState(() { /// Logic here! lastMessageIndex = lastMessageIndex + 25; _loadMoreMessage = true; }); } } }, child: ListView.builder( controller: _scrollController, reverse: true, itemCount: snapshot.data.length, itemBuilder: (context, index) { return ChatContent(listMessage[index]); }, ), ); } }, ),
Avez-vous de la chance avec la solution?
@Purus consultez ce projet github. Fonctionne très bien github.com/simplesoft-duongdt3/flutter_firestore_paging