Repository navigation
ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207
Description
Related: #153085
Update September 2023:
- New SliverVariedExtentList, ListView.extentBuilder for dynamic child sizing on demand
- New caching proposal, follow for updates:
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
-
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.
-
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.
-
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.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
dnfield commentedon Mar 9, 2020
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.
dnfield commentedon Mar 9, 2020
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.
esDotDev commentedon Mar 9, 2020
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.
firatcetiner commentedon Mar 14, 2020
May I ask how you implement such a logic? @esDotDev
VladyslavBondarenko commentedon Apr 2, 2020
Related to #24791
modulovalue commentedon Apr 7, 2020
Here's a second, simpler example that reproduces this issue. (copied over from #54195)
video (tested on macos)
leszekkrol commentedon Apr 17, 2020
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.
firatcetiner commentedon Apr 17, 2020
I'm offering a workaround related to this issue in here. I hope it will be useful for you.
esDotDev commentedon Apr 17, 2020
That looks promising, thanks!
klaszlo8207 commentedon Apr 22, 2020
Any news on this? Any workaround??
137 remaining items
Leffe108 commentedon May 17, 2024
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.
flutter-triage-bot commentedon Jun 7, 2024
The
triaged-desktoplabel is irrelevant if there is noteam-desktoplabel orfyi-desktoplabel.iapicca commentedon Aug 21, 2024
running the code sample in the issue
the app freezes at the step
4.con thing is the scrolling affecting the performance of the app,
another is to completely freezing the app rendering it unusable
mark8044 commentedon Sep 18, 2024
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
itemExtentBuildercorrect? 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).
gnprice commentedon Sep 19, 2024
Yeah,
itemExtentBuilderdoesn'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
itemExtentBuilderapplies, the current state of the thread is:There's a third-party package
super_sliver_list: ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207 (comment)That's probably the best thing to try if you're seeing this issue (and aren't in a use case where
itemExtentBuilderapplies).I think the core strategy that package is applying, or at least one of its core strategies, would be good to have in the framework: ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207 (comment)
For anyone who wants to make a solution to this happen in Flutter itself, that's the approach I'd suggest. It's not a small task.
There's talk of adding warnings in debug mode to help make developers aware when they're running into this issue: ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207 (comment)
I agree that'd be helpful.
iapicca commentedon Sep 19, 2024
@gnprice
the difference w/ and w/o prototypeItem is night and day
couldn't we just make it a
requiredparameter,so the developer consciously decide to have the
prototypenull (or not)therefore becoming aware of the performance implications?
bugrevealingbme commentedon Apr 6, 2025
iapicca commentedon Apr 13, 2025
@bugrevealingbme
are you experiencing the issue even using
prototypeItem? (code sample)ihijazi commentedon Jul 29, 2025
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.
n7trd commentedon Aug 1, 2025
I'm not entirely familiar with how the
SuperSliverListimplementation differs from Flutter's native one, or if it has any downsides I'm unaware of. That said, replacingSliverList.builderwithSuperSliverListcompletely 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.