title: Keep position in list with new items Flutter ListView
body: <p>I am building a Flutter app and have run into a problem with the ListView.builder.</p>
  
  <p>My current setup is a StreamBuilder which has a list of objects. Each object is bound to 1 item in the ListView.builder. </p>
  
  <p>My problem is that when new items are added and I called StreamController$add() the list is automatically scrolled to the top of the list. I am looking for the list position to be maintained and it is up to the user (or a button) to scroll to the top.</p>
  
  <p>I have added keys to the list items which helped other issues but not this one. I also added a PageStorageKey to the listview but that did not help. I have a scroll controller also but have not been able to figure out how to work this behavior. </p>
  
  <p>I can provide code samples if needed. Thanks in advance!</p>
  
  <p>Relevant code:</p>
  
  <pre><code>class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        theme: albatrossDarkTheme,
        home: TimelinePage(),
      );
    }
  }
  
  class TimelinePage extends StatefulWidget {
    TimelinePage({Key key}) : super(key: key);
  
    @override
    _TimelinePageState createState() =&gt; _TimelinePageState();
  }
  
  class _TimelinePageState extends State&lt;TimelinePage&gt; {
    AlbatrossClient _client;
    GlobalKey&lt;ScaffoldState&gt; _scaffoldKey = GlobalKey();
    PageStorageKey _pageKey = PageStorageKey(8);
    TimelineDatabase _database = TimelineDatabase();
    StreamController&lt;List&lt;Tweet&gt;&gt; _controller;
    ScrollController _scrollController;
  
    Stream&lt;List&lt;Tweet&gt;&gt; _getStream() {
      return _controller.stream;
    }
  
    Future&lt;void&gt; _refreshTimeline() async {
      _client.refreshTimeline().then((result) {
        if (result == 0)
          _database.getTimeline(false).then((update) {
            _controller.add(update);
          });
      });
    }
  
    _updateTimeline() async {
      _database.getTimeline(false).then((update) =&gt; _controller.add(update));
    }
  
    @override
    void initState() {
      _client = AlbatrossClient();
      _controller = StreamController&lt;List&lt;Tweet&gt;&gt;();
      _scrollController = ScrollController(keepScrollOffset: true);
      _database.getTimeline(false).then((saved) =&gt; _controller.add(saved));
        _client.refreshTimeline().then((result) {
          if (result == 0)
            _database.getTimeline(false).then((update) {
              _controller.add(update);
            });
        });
  
      super.initState();
    }
  
    @override
    void dispose() {
      _controller.close();
      _scrollController.dispose();
      super.dispose();
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        key: _scaffoldKey,
        appBar: SearchAppbar(() {
          _scaffoldKey.currentState.openDrawer();
        }),
        drawer: Drawer(
          child: DrawerMain.getDrawer(context, _client.getProfile()),
        ),
        body: Container(
            child: RefreshIndicator(
                onRefresh: _refreshTimeline,
                child: StreamBuilder&lt;List&lt;Tweet&gt;&gt;(
                  stream: _getStream(),
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return ListView.builder(
                        key: _pageKey,
                        controller: _scrollController,
                        itemCount:
                            snapshot.data != null ? snapshot.data.length : 0,
                        itemBuilder: (context, index) {
                          return RowTweet(
                              tweet: snapshot.data[index],
                              update: _updateTimeline,
                              animate: true);
                        },
                      );
                    } else
                      return Container(
                        alignment: Alignment(0.0, 0.0),
                        child: CircularProgressIndicator(),
                      );
                  },
                ))),
        floatingActionButton: FloatingActionButton(
          tooltip: 'Compose Tweet',
          onPressed: _composeTweet,
          foregroundColor: Colors.white,
          child: Image.asset(
            "icons/ic_tweet_web.webp",
            width: 38,
            height: 38,
          ),
        ), // This trailing comma makes auto-formatting nicer for build methods.
      );
    }
  }
  </code></pre>
  
  <p>Here is my new attempt:</p>
  
  <pre><code>return Scaffold(
        key: _scaffoldKey,
        appBar: SearchAppbar(() {
          _scaffoldKey.currentState.openDrawer();
        }),
        drawer: Drawer(
          child: DrawerMain.getDrawer(context, _client.getProfile()),
        ),
        body: Container(
            child: RefreshIndicator(
                onRefresh: _refreshTimeline,
                child: _timeline.isNotEmpty
                    ? ListView.builder(
                        key: _pageKey,
                        controller: _scrollController,
                        itemCount: _timeline.length,
                        itemBuilder: (context, index) {
                          return RowTweet(
                              tweet: _timeline[index],
                              update: _updateTimeline,
                              animate: true);
                        })
                    : Container(
                        alignment: Alignment(0, 0),
                        child: CircularProgressIndicator(),
                      ))),
        floatingActionButton: FloatingActionButton(
          tooltip: 'Compose Tweet',
          onPressed: _composeTweet,
          foregroundColor: Colors.white,
          child: Image.asset(
            "icons/ic_tweet_web.webp",
            width: 38,
            height: 38,
          ),
        ), // This trailing comma makes auto-formatting nicer for build methods.
      );
  </code></pre>