I'm building a chat application and I've reached the part where I'm creating the actual chat interface. I decided to use the ListView.builder() constructor, as I'm working with potentially a great amount of data (messages). The layout, and my code in general, look something like this.
I want to preserve the scrolling position at any time, unless the user explicitly scrolls away. Here comes the first issue: when the software keyboard appears, or the size of the input field changes, I would like the bottom of the messages area to stay visible, and the older (ie. above) messages to get pushed out of the screen. You know, the way it works in Messenger, etc. However, it doesn't work like that. The older messages stay visible, and the newer ones get pushed put.
To achieve this, I tried to set the reverse argument to true. While it indeed solved the above issues, it created a newer one. Whenever a new message is added (since it is "reversed", I have to prepend the messages list), the whole messages area appears to "jump". It is, I believe, due to the fact that the since ListView is reversed, the item with index 0 is always the newest message.
However, I don't like this behavior. It doesn't seem intuitive to the user, who either expects to be scrolled down to the latest message, or be shown a floating UI element indicating that there are new messages.
Question #1: can I somehow disable/change the above behavior for reversed list? I'm thinking of a callback that calculates the "old" offset and instantly scrolls to that location when a new message is added. Not sure if it's possible or if it fits the pattern.
Question #2: I though about not using a reversed list, and instead add something like a SizeChangedLayoutNotifier, listening to the size changes of the ListView, and scroll to position accordingly. This, too, looks hacky, though.
Generally speaking, I would like to achieve a Messenger-like (or really, any other similar app) behavior. Opening the software keyboard should "push up" the messages, and when a new message arrives, there shouldn't be any jump or automatic scrolling.
UPDATE #1: found a workaround, but I would rather not go with it. Whenever an item gets added, I get the position.maxScrollExtent value of the ScrollController, then, after updating the state, I get the new value. Subtract the new value from the old, then subtract the difference from the current offset, and finally jumpTo this value. And voilĂ , we are at the same message. This has multiple drawbacks, though:
- By the nature of this workaround, it's jumpy. You have to know that the new max. scroll extent will be to calculate the difference, because you have no way of knowing how long the new message is, and what the height of its bubble's going to be
- Race condition? You have to await for an arbitrary duration (for the lack of a better method, any takers?) in order to see the value update. It works in the emulator, but will it work on users' devices?
- It's not declarative/reactive. In my local example, I know when an item/message gets added. However, when using streams, I would need a listener to capture each time this happens, and it would be a lot of headache.
For the reasons above, I decided not to use this workaround.
Thanks.