Skip to content

ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207

Open

Description

@esDotDev

Related: #153085


Update September 2023:


Update from July 2023: #52207 (comment)


Update from September 2021: After much discussion, this #52207 (comment) suggestion emerged as a pretty good proposal. Basically, SliverList could short-circuit actually instantiating and laying out children if, once built, they are discovered to implement the PreferredHeight protocol. The next step is to implement this. -@Hixie


[√] Flutter (Channel master, v1.15.4-pre.241, on Microsoft Windows [Version 10.0.18362.657], locale en-US)

Here is an example (~80 lines) of a 10,000 item ListView, which attempts to uses a simple scrollbar to drive scroll position:
https://gist.github.com/esDotDev/792425c2cdfef947ce514b8ab70511e6

The performance is very poor on all platforms, Desktop, Web, Android, even when using profile mode on Android. iOS has not been tested.

I'm seeing frame render times up to 1300ms on Windows 10, with a Ryzen 3700x. Android, in profile mode, with a Snapdragon 845, has similar results. Visually, you can easily produce a 2-3 second lag by just scrolling up and down a couple of times.
http://screens.gskinner.com/shawn/2020-03-08_12-57-31.mp4

Setting the .itemExtent property solves the problem, but severely limits the flexibility of the list, no expanding cards, no ability to size rows to content, etc.

Problem in a nutshell
We need the ability to have many list items, of variable height, while retaining the ability to set scroll position efficiently. This is especially important on Web and Desktop which typically use scrollbars to traverse lists.

Proposed Solutions

  1. It would be nice if the list could handle this itself, seems like it calculates maxExtents each frame, when we really only need it calculating when the size of something has changed. Potentially this could be exposed so we could call it manually (scrollController.calculateExtents()) when we know something is changing.

  2. As a simpler workaround, you could expose .maxExtents so I as the programmer of the list can specify max extents with some basic math. ie, if I know my open cards are 40px heigh, and my closed are 20px, I can track open/close cards, and provide the list with the max extents very quickly, without it needing to measure.

  3. Could potentially leverage PreferredSizeWidget to reduce the calculation costs for the list. Then each list item can return it's own size, and the developer can come up with novel ways to reduce that calculation cost. For example, I could have my closed items all return a hardcoded value, while my "expanded items" use a LayoutBuilder to calculate their height. Or I could simply have multiple hardcoded values, and return the right one depending on the state of the renderer.

Activity

  1. dnfield commented on Mar 9, 2020

    @dnfield
    Contributor

    I think the core problem here is that, without knowing the item extent is fixed, we have no way of knowing that it won't dynamically change on each scroll, and so have to do all the work to build all the intermediate items so we can figure out scroll position and keep the scrollbar accurate.

    I suspect if you really profiled this down to the expensive methods, it's not just in calculating the max extents, but also in calculating the extent of each sliver that has to be rendered for the screen as you jump 1000s of items in the list. With a fixed extent list we can take a bunch of shortcuts. With a variable extent we can't.

    I suspect that suggestion 1 would not help because scrolling means something has changed, and we have no way of knowing that the application didn't decide to make certain items bigger or smaller as you scroll (imagine an expansion card that collapses when the user starts scrolling for example).

    Suggestion 2 would not help because we'd still need to calculate all the actual extents when you make a very large jump.

    Suggestion 3 might actually make things more expensive when you're using layout builders, since those will require a speculative layout pass in addition to the actual layout pass.

    We might be able to fudge something here and check how fast you're scrolling, and take some lower fidelity shortcut when scrolling very rapidly. But we'd have to really profile this to know where exactly the expensive parts are first.

  2. added
    f: scrollingViewports, list views, slivers, etc.
    frameworkflutter/packages/flutter repository. See also f: labels.
    perf: speedPerformance issues related to (mostly rendering) speed
    c: performanceRelates to speed or footprint issues (see "perf:" labels)
    on Mar 9, 2020
  3. added this to the milestone on Mar 9, 2020
  4. dnfield commented on Mar 9, 2020

    @dnfield
    Contributor

    At your application level, you might be able to do some similar shortcut, e.g. rebuild the listview with a fixed extent once the user starts scrolling fast, and then rebuild it again with a variable extent once they slow down/stop. But this would probably result ins ome weird jumpiness of the scroll bar.

  5. esDotDev commented on Mar 9, 2020

    @esDotDev
    Author

    Thanks for the thoughts, given the performance I'm seeing, > 1000ms frame times, it feels like a speculative pass that takes a ms or two, could be a viable tradeoff?

    Those were just my initial ideas, I'm sure theres many others. They key takeaway I think is that the algorithm chosen for drag-based scrolling on very large lists is not performant when used on desktop/web with scrollbar-based control.

    What I was trying to get at with my solutions, were to somehow offload the calculations to the renderer, so they could either be cached, or calculated by the list item, rather than having to go through the expensive layout pass. Obviously it would be ideal if the layout pass was cheap enough to just work, but if not, this could be an alternative approach.

    "we have no way of knowing that it won't dynamically change on each scroll, " To this end, it would be nice if there was someway for the application to inform the list that no, we will not change things during scroll, and in the rare case we we do, we will let you know so you can re-calculate. Then, in a worst-case scenario, we get what we have now... but in most cases, it would run really well.

    [Edit] Tried setting itemExtents while dragging large amounts, and removing when dragging small. Doesn't really work, it runs smooth initially, but then just grinds to a halt as soon as extents is removed.

  6. firatcetiner commented on Mar 14, 2020

    @firatcetiner

    [Edit] Tried setting itemExtents while dragging large amounts, and removing when dragging small. Doesn't really work, it runs smooth initially, but then just grinds to a halt as soon as extents is removed.

    May I ask how you implement such a logic? @esDotDev

  7. VladyslavBondarenko commented on Apr 2, 2020

    @VladyslavBondarenko

    Related to #24791

  8. modulovalue commented on Apr 7, 2020

    @modulovalue

    Here's a second, simpler example that reproduces this issue. (copied over from #54195)

    video (tested on macos)

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyHomePage());
    }
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: ListView.builder(
              itemCount: 10000,
              itemBuilder: (context, index) => Text("$index"),
            ),
          ),
        );
      }
    }
    
  9. leszekkrol commented on Apr 17, 2020

    @leszekkrol

    At the moment I have the same issue on severals projects. I noticed that problem is more apparent when you add physics: BouncingScrollPhysics() to ListView.builder.

  10. firatcetiner commented on Apr 17, 2020

    @firatcetiner

    I'm offering a workaround related to this issue in here. I hope it will be useful for you.

  11. esDotDev commented on Apr 17, 2020

    @esDotDev
    Author

    That looks promising, thanks!

  12. klaszlo8207 commented on Apr 22, 2020

    @klaszlo8207

    Any news on this? Any workaround??

  13. 137 remaining items

  14. Leffe108 commented on May 17, 2024

    @Leffe108

    What I find is missing is guidance or support for doing something where you in for example initState could create a virtual screen to layout widgets to pre cache for example height of 3 different widget types. Either sync or ofload to a async task. And once completed you can give itemExtentBuilder height of a few set of list item variants that you have in the list.

    I understand that a prototypeItemBuilder would be as expansive as laying out all, but there are use cases where you have only a few, but more than one, relevant prototypes.

  15. flutter-triage-bot commented on Jun 7, 2024

    @flutter-triage-bot

    The triaged-desktop label is irrelevant if there is no team-desktop label or fyi-desktop label.

  16. iapicca commented on Aug 21, 2024

    @iapicca

    running the code sample in the issue

    on thing is the scrolling affecting the performance of the app,
    another is to completely freezing the app rendering it unusable

  17. mark8044 commented on Sep 18, 2024

    @mark8044

    I never understood the point of itemExtentBuilder.

    Isn't the problem with variable extent lists that we dont actually know the height of the content thus we are doing a whole bunch of extra calculations just to arrive at the same place when the widget finally builds?

    For example, if I have a row in my list containing text, and that text might be 1 line or 3 lines tall, then my sliverlist item will be of different heights from one anoter. Now I have to come up with some crazy calculation to predict what height the sentance will force the widget to become to feed that to itemExtentBuilder correct? Just seems like a very roundabout and intensive way to solve this issue....

    Regardless this problem persists for seemingly very simple appearing list items (albeit with many rows and column so from a flutter POV its complex, even tho all text).

  18. gnprice commented on Sep 19, 2024

    @gnprice
    Member

    Isn't the problem with variable extent lists that we dont actually know the height of the content thus we are doing a whole bunch of extra calculations just to arrive at the same place when the widget finally builds?

    For example, if I have a row in my list containing text, and that text might be 1 line or 3 lines tall, […]

    Yeah, itemExtentBuilder doesn't really work for a use case where the heights depend on text layout, or on other complex factors.

    What it can work for is where the item widgets are complex to build but their heights are easy to predict: for example if they all have a fixed height, or if there are a handful of different types of items and each type has a fixed height. (Maybe there's one type of user-perceived item, and one type of separator. Or maybe some items are collapsed and others expanded. Or …)

    Outside of those use cases where itemExtentBuilder applies, the current state of the thread is:

  19. iapicca commented on Sep 19, 2024

    @iapicca

    @gnprice
    the difference w/ and w/o prototypeItem is night and day

    couldn't we just make it a required parameter,
    so the developer consciously decide to have the prototype null (or not)
    therefore becoming aware of the performance implications?

  20. bugrevealingbme commented on Apr 6, 2025

    @bugrevealingbme
  21. iapicca commented on Apr 13, 2025

    @iapicca

    @bugrevealingbme
    are you experiencing the issue even using prototypeItem? (code sample)

  22. ihijazi commented on Jul 29, 2025

    @ihijazi

    My temp workaround for this is instead of always using fixed heights, I implemented a conditional prototype system that only kicks in during animateTo/jumpTo when the user has scrolled significantly down the list.

    I modified SliverList.builder to use SliverPrototypeExtentList with say 350px fixed height prototype, and added condition: rapidScrollingNotifier, and kept normal SliverList.builder behavior for regular scrolling.

    The key here is rapidScrollingNotifier would be true before animation starts, and false once done.

    While animating/jumping, the visual impact is not noticeable (as it's fast animation). In my case, I only need to scroll to top / offset-zero, not specific item (but I would assume it would similarly with specific index).

    In addition, I first jump to 5000 (if offset > 5000), then animate. Helpful in really long/infinite list.

    Not perfect, but at least no ANRs or slow performance. Tested on low end device, with poor conditions, Galaxy A11, and was much better than before.

    My use case is an infinite list, with feed like list that has videos, photos, text, embedded posts, etc. So it's impossible to have fixed height.

  23. n7trd commented on Aug 1, 2025

    @n7trd

    I'm not entirely familiar with how the SuperSliverList implementation differs from Flutter's native one, or if it has any downsides I'm unaware of. That said, replacing SliverList.builder with SuperSliverList completely eliminated all scrolling jank in my app's long list—even in debug mode and without any further optimizations.

    I really hope the Flutter team takes a look at this, as it provides a much better out-of-the-box experience. I'm so glad I stumbled upon it.

    Edit: By scrolling, I mean using the scrollbar. The difference is clearly visible in their demo and in my app on the desktop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: desktopRunning on desktopc: performanceRelates to speed or footprint issues (see "perf:" labels)customer: crowdAffects or could affect many people, though not necessarily a specific customer.f: scrollingViewports, list views, slivers, etc.found in release: 3.16Found to occur in 3.16found in release: 3.18Found to occur in 3.18frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onperf: speedPerformance issues related to (mostly rendering) speedteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions