Repository navigation
[framework] [scrolling] [performance] ScrollController abnormal behavior with long lists #153085
Description
Related: #52207
I'm opening this issue because as follow up on the issue below
the behavior of ScrollController shows some anomalies, please follow the steps below
Steps to reproduce
-
run in release mode the code sample provided on android device (likely other platform experience the same issue)
-
with only text in the list
a. tap the fab~163sand observe fair performances
b. tap the fab~5sand observe poor performances
c. tap the fabresetand observe bad performances -
tap the
flutter logoto change the content of the list -
with flutter logo in the list
a. tap the fab~163sand observe fair performances
b. tap the fab~5sand observe very bad performances
. (a lot of time between the end of the scrolling and thesnackbar, see video)
c. tap the fabresetand observe catastrophic performances (sometimes freezes the app permanently)
Expected results
ScrollViewControllershould handle "fast animations" and "jumps" more gracefully- the time set in
animateToshould be "respected" animateToshould return immediately after the scrolling is overjumpToshouldn't freeze the app
Actual results
ScrollViewControllerhandles "fast animations" and "jumps" poorly
(see ListView: Poor performance with many variable-extent items + jumpTo (scroll bar, trackpad, mouse wheels) #52207)- the time set in
animateToin some cases is disregarded - occasionally
animateToreturns long after the scrolling is over jumpTooccasionally freeze the app
(see comment)
Code sample
Code sample
import 'package:flutter/material.dart';
/// roughly a frame at 60 fps
const _microsecondsPerFrameAt60fps = 16333;
const _arbitraryScrollDuration = Duration(seconds: 5);
const _itemCount = 10000;
const _sixtyFPSDuration = Duration(
microseconds: _microsecondsPerFrameAt60fps * _itemCount,
);
void main() => runApp(
const MaterialApp(
showPerformanceOverlay: true,
home: MyHomePage(),
),
);
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _hasIcon = false;
late final ScrollController _controller;
@override
void initState() {
super.initState();
_controller = ScrollController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleIcon() => setState(() => _hasIcon = !_hasIcon);
@override
Widget build(context) => Scaffold(
body: ListView.builder(
controller: _controller,
itemCount: _itemCount,
itemBuilder: (context, index) {
final textWidget = Expanded(
flex: 1,
child: Text('$index', textAlign: TextAlign.center),
);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
textWidget,
for (var i = 0; i < 5; i++) ...[
_hasIcon
? const Expanded(
flex: 1,
child: FlutterLogo(),
)
: textWidget,
]
],
);
},
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FloatingActionButton(
onPressed: () => _controller.jumpTo(0),
child: const Text('reset'),
),
TimerButton(
controller: _controller,
duration: _arbitraryScrollDuration,
),
TimerButton(
controller: _controller,
duration: _sixtyFPSDuration,
),
FloatingActionButton(
onPressed: _toggleIcon,
child: _hasIcon
? const Icon(Icons.text_increase_outlined)
: const FlutterLogo(),
),
],
),
);
}
class TimerButton extends StatelessWidget {
final ScrollController controller;
final Duration duration;
const TimerButton({
super.key,
required this.controller,
required this.duration,
});
@override
Widget build(context) => FloatingActionButton(
onPressed: () async {
final start = DateTime.now();
await controller.animateTo(
controller.position.maxScrollExtent,
duration: duration,
curve: Curves.linear,
);
if (!context.mounted) {
return;
}
final actualDuration = DateTime.now().difference(start);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('expected duration: ${duration.inMilliseconds} ms\n'
'actual duration: ${actualDuration.inMilliseconds}'),
),
);
},
child: Text('~${duration.inSeconds} s'),
);
}Screenshots or Video
Screenshots / Video demonstration
video_sample.mp4
Logs
No response
Flutter Doctor output
Doctor output
flutter doctor -v
[✓] Flutter (Channel stable, 3.24.0, on macOS 14.5 23F79 darwin-arm64, locale en-US)
• Flutter version 3.24.0 on channel stable at /Users/francesco/fvm/versions/stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 80c2e84975 (9 days ago), 2024-07-30 23:06:49 +0700
• Engine revision b8800d88be
• Dart version 3.5.0
• DevTools version 2.37.2
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
• Android SDK at /Users/francesco/Library/Android/sdk
• Platform android-34, build-tools 34.0.0
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 15F31d
• CocoaPods version 1.15.2
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2022.3)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
[✓] VS Code (version 1.92.0)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.94.0
[✓] Connected device (4 available)
• Pixel 3a (mobile) • 965AY0WP5C • android-arm64 • Android 12 (API 32)
• macOS (desktop) • macos • darwin-arm64 • macOS 14.5 23F79 darwin-arm64
• Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.5 23F79 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 120.0.6099.71
[✓] Network resources
• All expected network resources are available.
• No issues found!
Activity
darshankawar commentedon Aug 9, 2024
Thanks for the detailed report @iapicca
I was able to replicate and see the same behavior upon running on device as well as emulator with latest versions.
Piinks commentedon Aug 20, 2024
This seems like a duplicate of #52207
This is because the list is trying to lay out all of the items that are flying by (or at least trying to) so quickly. Everything from the start to the destination must be built so we know what to show at the given location, and it is causing these performance issues. I think we can close this to refer to the #52207 ?
iapicca commentedon Aug 21, 2024
Hi @Piinks
I updated the initial post delegating the performance issue to the issue you mentioned above
and added a comment regarding the "freezing"
I feel that the other 2 aspects are worth investigating and,
as pointed out in the title, somehow related to ScrollController
animateToin some cases is disregardedthis is self explanatory
animateToreturns long after the scrolling is overthis is very strange in my opinion, as ScaffoldMessenger's showSnackBar
is called much later than the time collected by
actualDurationthis "feels" unrelated to
scrollingand more relevant torendering(imho)this behavior is easily reproducible in the step
4.b(or4.cif doesn't explode)Piinks commentedon Aug 21, 2024
IMO it is not actually the scroll controller causing this. It is the demand to lay out so many widgets all at once that causes all of these issues, which is being tracked in that other issue I linked. We can leave this open though. Thanks for providing more details.
iapicca commentedon Sep 19, 2024
@Piinks the issue does not occur adding prototypeItem
I'm closing in favor of
Code sample
github-actions commentedon Oct 4, 2024
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.