Search code examples
fluttergoogle-cloud-firestoreflutter-listview

Prevent listview auto scroll to bottom when new snapshot is fetched from Firestore stream


I am creating a flutter app that has realtime in app messaging feature. I've managed to create a reversed listview that listens to snapshots in the firestore collection and new text messages will get added to the listview without any errors. I've also added listener to the scrollController to load older messages when user scrolls to the top of the listview. The older messages gets added into the list of text messages without any error. However, when this happens, the listview automatically scrolls to the bottom(Latest message) instead of staying in the current position. How can i prevent the listview from auto scrolling to the bottom?

Below are my codes and the gif to show the listview snaps to the bottom: enter image description here

class MessagingPage extends StatefulWidget {
  const MessagingPage({Key? key}) : super(key: key);

  @override
  State<MessagingPage> createState() => _MessagingPageState();
}

class _MessagingPageState extends State<MessagingPage> {
  int limit = 20;
  List messages = [];
  final ScrollController _scrollController = ScrollController();
  final TextEditingController _textEditingController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      if(_scrollController.position.pixels == _scrollController.position.maxScrollExtent
      && limit <= messages.length && !_scrollController.position.outOfRange){
        setState(() {
          limit += 10;
        });
      }
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: Column(
          children: [
            InkWell(
              onTap: () => Navigator.pop(context),
              child: Container(
                height: 50,
                width: 50,
                color: Colors.orange,
                child: Icon(Icons.arrow_back),
              ),
            ),
            Container(
              height: MediaQuery.of(context).size.height*0.2,
              width: MediaQuery.of(context).size.width,
              color: Colors.grey[300],
              child: StreamBuilder(
                  stream: getChatStream(limit),
                  builder: (context, snapshot){
                    if(snapshot.connectionState == ConnectionState.waiting){
                      return Center(child: CircularProgressIndicator());
                    } else {
                      if(snapshot.hasData){
                        messages = snapshot.data!.docs;
                        print('the_length: ${messages.length}');

                        if(messages.length > 0){
                          return ListView.builder(
                              controller: _scrollController,
                              padding: EdgeInsets.all(10),
                              itemCount: messages.length,
                              reverse: true,
                              itemBuilder: (context, index){
                                print('index: $index');


                                if(snapshot.connectionState == ConnectionState.waiting) {
                                  return Center(child: CircularProgressIndicator());
                                } else {
                                  return buildItem(index, messages[index]);
                                }

                              }
                          );
                        } else {
                          return Center(child: Text('No messages here yet...'));
                        }
                      } else {
                        return Center(child: Text('No messages here yet...'));
                      }
                    }
                  }),
            ),
            textBar(_textEditingController),
            
          ],
        ),
      ),
    );
  }
}

Solution

  • Fixed the problem here. Because everytime the snapshot's connectionState changes to waiting (which is fetching new data), it build the circularprogressindicator and when its done, a new ListView.builder gets rebuilt again. Hence, resulting the scrollController to start from index 0.

    Follow amendments fixed it:

    StreamBuilder<QuerySnapshot>(
                      stream: getChatStream(limit),
                      builder: (context, snapshot) {
                        if (snapshot.hasData) {
                          messages = snapshot.data!.docs;
                         
    
                          return ListView.builder(
                            controller: _scrollController,
                            padding: EdgeInsets.all(10),
                            itemCount: messages.length,
                            reverse: true,
                            addAutomaticKeepAlives: true,
                            addRepaintBoundaries: true,
                            itemBuilder: (context, index) {
                              return buildItem(index, messages[index]);
                            },
                          );
                        } else if(snapshot.connectionState == ConnectionState.waiting){
                          return Center(child: CircularProgressIndicator());
                        } else {
                          return Center(child: Text('No messages here yet...'));
                        }
                      },
                    )