How to fix 'jumping to top of each item' while scrolling up in Flutter ListView of FutureBuilder items?

Issue

I just started learning Flutter through a course and one of the projects is to code a Hacker News reader app. Two HTTP requests are needed, one to get the top list of ids and one for each article using its id.

Contrary to Google’s The Boring Show version, I need to be able to request each article as the user scrolls down to the total number of top articles (~500). I also wanted to try a simpler way than the many streams, transformers, and workarounds that were used in my course.

I’m using a StreamBuilder to get the top ids from a stream through a bloc and a FutureBuilder for each row of a ListView.builder where the article is fetched from that bloc. The data is either pulled from an HTTP request or a cache (a sqflite database).

It works perfectly while scrolling down but, when scrolling up, it jumps to the top of each item except for the first 4 items (because they’re already built at that point).

I’ve tried different structures and patterns for days and it always behaves the same way whenever I use a FutureBuilder inside the ListView.builder.
Tried it in the emulator and on my Pixel 3 XL in debug and release mode.

The Boring Show method of sending a limited number of articles from a StreamBuilder to a ListView.builder works because a FutureBuilder is not needed but there is no way to fetch more articles.

By the way, from what I can tell, the items are NOT re-fetched while scrolling up even though ConnectionState is set to waiting for every build.

I’ve added a video of the issue on YouTube. Hope it works:

YouTube Video of ListView issue while scrolling up

Here is the class where the list is displayed. I combined the widgets to make it compact:

class TopList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final StoriesBloc bloc = StoriesProvider.of(context);
    return StreamBuilder<List<int>>(
        stream: bloc.topList,
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemBuilder: (context, int index) {
                    return FutureBuilder<ItemModel>(
                        key: Key(snapshot.data[index].toString()),
                        future: bloc.getNewsItem(snapshot.data[index]),
                        builder: (context, snapshot) {
                          switch (snapshot.connectionState) {
                            case ConnectionState.none:
                            case ConnectionState.active:
                            case ConnectionState.waiting:
                              print('${snapshot.connectionState} for $index');
                              return Text('Loading...');
                            case ConnectionState.done:
                              return buildItem(snapshot.data, index);
                          }
                        });
                  },
                )
              : Center(
                  child: CircularProgressIndicator(),
                );
        });
  }

  Widget buildItem(ItemModel item, int index) {
    print('Building row $index');
    return ListTile(
      key: Key(index.toString()),
      leading: CircleAvatar(
        child: Text('${index + 1}', style: TextStyle(fontSize: 14)),
        radius: 16,
      ),
      title: Text(item.title),
      subtitle: Text('${item.score}'),
      trailing: Column(
        children: <Widget>[
          Icon(Icons.comment),
          Text('${item.descendants}'),
        ],
      ),
    );
  }
}

from flutter doctor:

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel dev, v1.5.8, on Microsoft Windows [Version 10.0.17763.437], locale en-US)

[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[√] Android Studio (version 3.4)
[√] Android Studio (version 3.5)
[√] VS Code (version 1.32.3)
[√] Connected device (1 available)

• No issues found!

I expected scrolling up and down to be smooth and a ‘Loading…’ text to show up for items that are loading.

Here are print outputs of activities while scrolling down than up:

I/flutter (13405): ConnectionState.waiting for 17
I/flutter (13405): Building row 17
I/flutter (13405): ConnectionState.waiting for 18
I/flutter (13405): Building row 18
I/flutter (13405): ConnectionState.waiting for 19
I/flutter (13405): Building row 19
I/flutter (13405): ConnectionState.waiting for 20
I/flutter (13405): Building row 20
I/flutter (13405): ConnectionState.waiting for 21
I/flutter (13405): Building row 21
I/flutter (13405): ConnectionState.waiting for 22
I/flutter (13405): Building row 22
I/flutter (13405): ConnectionState.waiting for 23
I/flutter (13405): Building row 23
I/flutter (13405): ConnectionState.waiting for 6
I/flutter (13405): Building row 6
I/flutter (13405): ConnectionState.waiting for 5
I/flutter (13405): Building row 5
I/flutter (13405): ConnectionState.waiting for 4
I/flutter (13405): Building row 4
I/flutter (13405): ConnectionState.waiting for 3
I/flutter (13405): Building row 3
I/flutter (13405): ConnectionState.waiting for 2
I/flutter (13405): Building row 2
I/flutter (13405): ConnectionState.waiting for 1
I/flutter (13405): Building row 1
I/flutter (13405): ConnectionState.waiting for 0
I/flutter (13405): Building row 0

The problem might be obvious but I can’t figure it out.

Solution

Fixed it!

Alright, got a suggestion from ‘buy dip kek’ at the flutter Discord group to place the exact same widget, a ListTile, with dummy data while the future from each FutureBuilder is loading. That fixed the issue.

Answered By – RichR

Answer Checked By – Mildred Charles (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *