Flutter - Quickly implement the effect of the chat session list, perfect 💯

LinXunFeng
4 min readAug 20, 2023

--

Target effect

List Effects for Chat Session Page

  1. When the chat data is not enough for one screen, all the chat data will be displayed on the top
  2. When inserting a message
  • Inserting a message pushes the list up if the latest message is near the bottom of the list
  • If the latest message is not immediately at the bottom of the list, pin to the current chat position

The effect is shown in the figure:

Principle

  1. The method involved

ScrollPhysics provides the adjustPositionForNewDimensions method, which is used to correct the offset of ScrollView after rebuild. The method declaration is as follows

double adjustPositionForNewDimensions({
required ScrollMetrics oldPosition,
required ScrollMetrics newPosition,
required bool isScrolling,
required double velocity,
})

By default, the value is the last offset, that is the pixels of the newPosition parameter, so when a message is inserted at the bottom, the message list will scroll along.

As shown in the figure below, observe the blue message entries, each time a message is inserted, all the messages will automatically go up, and the offset of the scroll view has not changed~

Note: It is worth noting that if the value returned by this method is not equal to the pixels of newPosition, it will trigger the re-layout of the view, so this operation is still relatively expensive, and the change of the return value should be minimized.

2. Implement logic

Based on the above content, it is not difficult to deduce that the effect of inserting messages is implemented as follows:

  1. When the message is close to the bottom of the ListView, inserting a message will push the list up (here directly we return the value of super, which is the pixels of the newPosition parameter)
  2. Pin to the current chat position if not immediately at the bottom of the ListView (we return the latest offset of the original 0th message)

Let’s focus on the implementation logic of returning the latest offset of the original first message in point 2:

The essence of ListView is RenderSliverList, and the first item rendered in the current ListView is obtained through the firstChild property of RenderSliverList.

As shown in the figure below, firstChild is an item with a index of 10. This item is related to the cacheExtent in the pre-rendering area. If it is set to 0, the index of firstChild will be 12. I believe it is not difficult to understand.

Therefore, we only need to record the offset of the 0th message when inserting a message. When the ListView is rebuilt, the adjustPositionForNewDimensions method will be called. At this time, the offset of the 1st message is taken out, and the difference between the two plus the value of super is the target correction offset.

As for when the chat data is not enough for one screen, all the chat data is displayed on the top. This effect is just switching shrinkWrap, which is relatively simple and will not be discussed here.

I have encapsulated the above logic and integrated it into flutter_scrollview_observer. Next, let’s see how to use it.

Usage

Now it only takes three steps to quickly achieve the effect of chat conversation list

Step 1: Initialize the necessary ListObserverController and ChatScrollObserver

/// Initialize ListObserverController
observerController = ListObserverController(controller: scrollController)
..cacheJumpIndexOffset = false;

/// Initialize ChatScrollObserver
chatObserver = ChatScrollObserver(observerController)
..toRebuildScrollViewCallback = () {
// Here you can only rebuild the specified ScrollView
setState(() {});
};

Step 2: Configure ListView as follows and wrap it with ListViewObserver

Widget _buildListView() {
Widget resultWidget = ListView.builder(
physics: ChatObserverClampinScrollPhysics(observer: chatObserver),
shrinkWrap: chatObserver.isShrinkWrap,
reverse: true,
controller: scrollController,
...
);

resultWidget = ListViewObserver(
controller: observerController,
child: resultWidget,
);
return resultWidget;
}

Step 3: Before inserting or deleting messages, call the standby method of ChatScrollObserver

onPressed: () {
chatObserver.standby();
setState(() {
chatModels.insert(0, ChatDataHelper.createChatModel());
});
},
...
onRemove: () {
chatObserver.standby(isRemove: true);
setState(() {
chatModels.removeAt(index);
});
},

Note: The setState in the example can be replaced with the code for partial refresh of the ListView.

--

--