skip to Main Content

When I add some items on top of the ListView it scrolls to the top item on the 0 index. But I need it to stay in the same position as before adding items.

For example, chat history pagination on the top of the list of messages, if I open any messenger (Telegram, WhatsApp, etc.) open chat with a long history and scroll down downloading the history. History will be added to the top of the list (from the server ~20 messages at a time) but the list will stay on the same position (while scrolling).

Flutter ListView behaves like that if you add to the bottom, but if you add to the top it jumps to the first added item. I want it to stay.

2

Answers


  1. Screenshot:

    enter image description here


    Since you didn’t share any code, I just created a simple demo to show how you can achieve the effect like a messaging app. The code is very simple to understand, I have used comments almost everywhere.

    Code (Null safe):

    class _MyPageState extends State<MyPage> {
      // Say you have total 100 messages, and you want to load 20 messages on each scroll.
      final int _totalMessages = 100, _loadInterval = 20;
      final double _loadingOffset = 20;
      late final List<String> _messages;
      bool _loading = false;
      final ScrollController _controller = ScrollController();
    
      @override
      void initState() {
        super.initState();
    
        // Initially, load only 20 messages.
        _messages = List.generate(20, (i) => 'Message   #${_totalMessages - i}');
        _controller.addListener(_scrollListener);
      }
    
      void _scrollListener() async {
        var max = _controller.position.maxScrollExtent;
        var offset = _controller.offset;
    
        // Reached at the top of the list, we should make _loading = true
        if (max - offset < _loadingOffset && !_loading) {
          _loading = true;
    
          // Load 20 more items (_loadInterval = 20) after a delay of 2 seconds
          await Future.delayed(Duration(seconds: 2));
          int lastItem = _totalMessages - _messages.length;
          for (int i = 1; i <= _loadInterval; i++) {
            int itemToAdd = lastItem - i;
            if (itemToAdd >= 0) _messages.add('Message   #$itemToAdd');
          }
    
          // Items are loaded successfully, make _loading = false
          setState(() {
            _loading = false;
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Messages')),
          body: ListView.builder(
            controller: _controller,
            reverse: true,
            itemCount: _messages.length + 1, // +1 to show progress indicator.
            itemBuilder: (context, index) {
              // All messages fetched. 
              if (index == _totalMessages) return Container();
    
              // Reached top of the list, show a progress indicator. 
              if (index == _messages.length) return Align(child: CircularProgressIndicator());
    
              // Show messages. 
              return ListTile(title: Text('${_messages[index]}'));
            },
          ),
        );
      }
    }
    
    Login or Signup to reply.
  2. The solution proposed by @CopsOnRoad works if your feed can expand in one direction only.

    This solution works for appending items to the top and the bottom of the list. The idea of the solution is the following. You create two SliverLists and put them inside CustomScrollView.

    CustomScrollView(
      center: centerKey,
      slivers: <Widget>[
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
                return Container(
                  // Here we render elements from the upper group
                  child: top[index]
                )
            }
        ),
        SliverList(
          // Key parameter makes this list grow bottom
          key: centerKey,
          delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
                return Container(
                  // Here we render elements from the bottom group
                  child: bottom[index]
                )
            }
        ),
    )
    

    The first list scrolls upwards while the second list scrolls downwards. Their offset zero points are fixed at the same point and never move. If you need to prepend an item you push it to the top list, otherwise, you push it to the bottom list. That way their offsets don’t change and your scroll view does not jump.
    You can find the solution prototype in the following dartpad example.

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