skip to Main Content

I’d like to go to the bottom in the list builder, on the last message and auto scroll to the bottom. I would like the newer messages to the bottom like WhatsApp or other chat.

I’m tested a lot of solution without success. Please let me know how can I solve this issue.

Thanks you.

here the code :

 Future<ResponseGetDiscussionMessage> getDiscussionMessage(
    {required idDiscussion}) async {
  ResponseGetDiscussionMessage? responseGetDiscussionMessage;

  Map<String, dynamic> customBody = {
    "action": 28,
    "idDiscussion": idDiscussion,
    "offset": 0,
    "limit": null
  };

  var res = await ApiCallPost().apiCall(customBody);

  return ResponseGetDiscussionMessage.fromJson(res);
}
(...)

final ScrollController _scrollController = ScrollController();
@override
  void initState() {
    super.initState();
    futureCreateDiscussion =
        createDiscussion(name: "", idUser: widget.varToPage);

    // Scroll to bottom after the first frame is rendered
    WidgetsBinding.instance.addPostFrameCallback((_) => scrollToBottom());
  }

  // Method to scroll to the bottom
  void scrollToBottom() {
    if (_scrollController.hasClients) {
      _scrollController.animateTo(
        _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeOut,
      );
    }
  }
(...)
 @override
  void dispose() {
    // TODO: implement dispose
    _textNotifier.dispose();
    _controllerEnvoieMessage.dispose();

    _scrollController.dispose();

    super.dispose();
  }

SizedBox(
                            child: Column(
                              children: [
                                FutureBuilder<ResponseCreateDiscussion>(
                                    future: futureCreateDiscussion,
                                    builder: (BuildContext context, snapshot) {
                                      if (snapshot.connectionState ==
                                          ConnectionState.waiting) {
                                        return const Center(
                                            child: CircularProgressIndicator());
                                      } else if (snapshot.hasError) {
                                        return Center(
                                            child: Text(
                                                'Erreur : ${snapshot.error}'));
                                      } else {
                                        discussionDetail['idDiscussion'] =
                                            snapshot.data!.result.id;
                                        futureGetDiscussionMessage =
                                            getDiscussionMessage(
                                                idDiscussion: discussionDetail[
                                                    'idDiscussion']);

                                        return Container(
                                          child: Column(
                                            children: [
                                              Container(
                                                  child: FutureBuilder<
                                                          ResponseGetDiscussionMessage>(
                                                      future:
                                                          futureGetDiscussionMessage,
                                                      builder:
                                                          (BuildContext context,
                                                              snapshot) {
                                                        if (snapshot
                                                                .connectionState ==
                                                            ConnectionState
                                                                .waiting) {
                                                          return const Center(
                                                              child:
                                                                  CircularProgressIndicator());
                                                        } else if (snapshot
                                                            .hasError) {
                                                          return Center(
                                                              child: Text(
                                                                  'Erreur : ${snapshot.error}'));
                                                        } else {
                                                          return ListView
                                                              .builder(
                                                                  //  controller: _scrollController,
                                                                  controller:
                                                                      _scrollController,
                                                                  //  reverse: true,
                                                                  itemCount: snapshot
                                                                          .data
                                                                          ?.result
                                                                          .length ??
                                                                      0,
                                                                  scrollDirection:
                                                                      Axis
                                                                          .vertical,
                                                                  shrinkWrap:
                                                                      true,
                                                                  physics:
                                                                      const NeverScrollableScrollPhysics(),
                                                                  itemBuilder:
                                                                      (BuildContext
                                                                              contex,
                                                                          int index) {
                                                                    String? lastUser = index ==
                                                                            0
                                                                        ? null
                                                                        : snapshot
                                                                            .data!
                                                                            .result[index -
                                                                                1]
                                                                            .firstname;

                                                                    bool isFirstMessageForUser = lastUser !=
                                                                        snapshot
                                                                            .data!
                                                                            .result[index]
                                                                            .firstname;

                                                                    return Align(
                                                                        alignment: snapshot.data!.result[index].isOwner ==
                                                                                false
                                                                            ? Alignment.topLeft
                                                                            : Alignment.topRight,
                                                                        child: Container(
                                                                            margin: const EdgeInsets.only(right: 15, left: 15),
                                                                            constraints: BoxConstraints(
                                                                              maxWidth: MediaQuery.of(context).size.width * 0.8,
                                                                            ),
                                                                            child: IntrinsicWidth(
                                                                                child: Card(
                                                                                    color: ColorsDiolos.vertClair,
                                                                                    elevation: 0,
                                                                                    child: Container(
                                                                                        padding: const EdgeInsets.only(left: 15, right: 15, top: 5, bottom: 5),
                                                                                        alignment: snapshot.data!.result[index].isOwner == false ? Alignment.topLeft : Alignment.topRight,
                                                                                        child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
                                                                                          Text("${snapshot.data!.result[index].date}"),
                                                                                          if (isFirstMessageForUser)
                                                                                            Row(
                                                                                              children: [
                                                                                                CircleAvatar(
                                                                                                  radius: 20,
                                                                                                  backgroundImage: NetworkImage("$urlSite/file/user-profile/${snapshot.data!.result[index].image}"),
                                                                                                ),
                                                                                                Text(
                                                                                                  "${snapshot.data!.result[index].firstname}",
                                                                                                  style: const TextStyle(
                                                                                                    color: ColorsDiolos.violetClair,
                                                                                                    fontWeight: FontWeight.bold,
                                                                                                  ),
                                                                                                ),
                                                                                              ],
                                                                                            ),
                                                                                          Align(
                                                                                              alignment: snapshot.data!.result[index].isOwner == false ? Alignment.topLeft : Alignment.topRight,
                                                                                              child: Text(
                                                                                                "${snapshot.data!.result[index].data!.text}",
                                                                                                style: TextStyle(color: ColorsDiolos.violetFonce, fontWeight: FontWeight.bold, fontSize: (snapshot.data!.result[index].data!.text!.length > 2) || (isEmoji("${snapshot.data!.result[index].data!.text}") == false) ? 17 : 60),
                                                                                              ))
                                                                                        ]))))));
                                                                  });
                                                        }
                                                      }))
                                            ],
                                          ),
                                        );
                                      }
                                    }),
                              ],
                            ),
                          ),
                        

(...)

I would like to read the message with the last to the top and last to the bottom.

2

Answers


  1. Chosen as BEST ANSWER

    Solved. I put the controller on SingleChildScrollView() and not with ListView.builder

    Thank you,


  2. To implement auto-scrolling in a ListView like a chat application, where new messages appear at the bottom, here are a few steps that should help achieve the desired behavior.

    Solution Steps

    1.  Reverse the ListView so new messages appear at the bottom.
    2.  Use a Scroll Controller to handle the auto-scrolling logic.
    3.  Ensure auto-scrolling occurs after the widget frame is built, especially when new messages are added.
    

    Complete Code Solution

    Here’s an updated version of your code that includes these steps. It uses WidgetsBinding.instance.addPostFrameCallback to scroll after the UI frame is rendered, ensuring smooth scrolling to the bottom whenever a new message arrives.

    import 'package:flutter/material.dart';
    
    class ChatScreen extends StatefulWidget {
      final String varToPage;
    
      ChatScreen({required this.varToPage});
    
      @override
      _ChatScreenState createState() => _ChatScreenState();
    }
    
    class _ChatScreenState extends State<ChatScreen> {
      final ScrollController _scrollController = ScrollController();
      late Future<void> futureCreateDiscussion;
    
      @override
      void initState() {
        super.initState();
        futureCreateDiscussion = createDiscussion(name: "", idUser: widget.varToPage);
        
        // Scroll to bottom after the first frame is rendered
        WidgetsBinding.instance.addPostFrameCallback((_) => scrollToBottom());
      }
    
      // Method to scroll to the bottom
      void scrollToBottom() {
        if (_scrollController.hasClients) {
          _scrollController.animateTo(
            _scrollController.position.maxScrollExtent,
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeOut,
          );
        }
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Chat")),
          body: FutureBuilder(
            future: futureCreateDiscussion,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                return Center(child: Text("Error loading messages"));
              } else {
                return ListView.builder(
                  controller: _scrollController,
                  itemCount: snapshot.data?.result.length ?? 0,
                  physics: const AlwaysScrollableScrollPhysics(),
                  itemBuilder: (BuildContext context, int index) {
                    final message = snapshot.data!.result[index];
                    final isOwner = message.isOwner;
                    final alignment = isOwner ? Alignment.topRight : Alignment.topLeft;
    
                    return Align(
                      alignment: alignment,
                      child: Container(
                        margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
                        constraints: BoxConstraints(
                          maxWidth: MediaQuery.of(context).size.width * 0.8,
                        ),
                        child: Card(
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(message.content),
                          ),
                        ),
                      ),
                    );
                  },
                );
              }
            },
          ),
        );
      }
    
      Future<void> createDiscussion({required String name, required String idUser}) async {
        // Your async logic to create a discussion goes here.
      }
    }
    

    Explanation of Changes:

    •   WidgetsBinding.instance.addPostFrameCallback: By using this in initState, the scrolling happens after the widget tree has been built, ensuring the view starts at the bottom after initial loading.
    •   Updated scrollToBottom Method: This method checks if _scrollController has clients before scrolling, avoiding errors when the list view isn’t ready.
    

    With this setup, each time a new message is added, the ListView will scroll to the latest message at the bottom. This simulates the chat behavior you see in messaging apps like WhatsApp.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search