The app is silk on your test device and a slideshow on your customer’s three-year-old Android. That gap isn’t Flutter being slow: Flutter renders at 60fps on remarkably weak hardware when the code lets it. Flutter app jank on older phones is almost always the app doing avoidable work on the one thread that can’t afford it.
Fast phones forgive wasteful code. Old phones invoice you for every frame, and your users hold the old phones.

Rebuilds you didn’t mean to trigger
The most common cause: giant widgets rebuilt wholesale on every small state change. A setState high in the tree rebuilds every child below it, sixty times a second during animation. On a flagship the waste is invisible; on a budget chip it’s dropped frames.
The fixes are structural. Push state down so rebuilds touch the smallest possible subtree, split monolithic build methods into focused widgets, and use const constructors everywhere the compiler allows, because const widgets skip rebuilds entirely. Selective listening (select in Provider/Riverpod, granular builders) stops your UI from redrawing because an unrelated field changed.
Lists built the expensive way
A scrolling list built with a plain Column or a ListView without the .builder constructor lays out every item up front: hundreds of widgets for the twelve on screen. Older devices pay that bill in startup stutter and scroll jank. ListView.builder and friends build only what’s visible; fixed itemExtent where item heights are uniform makes scroll math nearly free. This one refactor fixes more budget-phone jank than any other single change.
Images doing GPU work per frame
Full-resolution photos decoded into small thumbnails burn memory and decode time old phones don’t have. Give Flutter the target size with cacheWidth/cacheHeight so decoding happens once at the right resolution.
And treat certain "free" effects as luxury items: Opacity wrapping complex subtrees, BackdropFilter blurs, and shadows not isolated in a RepaintBoundary force extra render work that flagship GPUs shrug off and budget GPUs visibly can’t.
Animations that rebuild more than they move
A common source of jank is an animation driven by setState in a large widget. Every tick rebuilds the whole screen just to move one element. Use AnimatedBuilder and pass the parts that never change as its child, so only the moving piece rebuilds each frame.
The same logic applies to fades. Animating an Opacity widget is costly, while FadeTransition or AnimatedOpacity are built for the job. Avoid animating clips and blurs where a simpler effect would do.

Heavy work on the UI thread
JSON parsing a large response, image processing, database queries in a build path: anything over a few milliseconds on the main isolate steals frames. Move real computation to a background isolate with compute(), and keep build methods pure layout.
How to find Flutter app jank in DevTools
Guessing wastes time, because jank usually has one or two real sources. Profiling tells you which kind of problem you have before you start rewriting.
- Run the app with
flutter run --profileon the slowest phone you can find. - Open DevTools and go to the Performance view, then use the screen that feels janky.
- Look at which thread is over budget. Slow UI thread frames point at build, layout and Dart work. Slow raster thread frames point at expensive painting, such as opacity, clips and blurs.
- Turn on rebuild tracking to see which widgets rebuild far more often than they should.
- Fix the worst offender, then measure again before touching anything else.
Flutter’s guide to profiling UI performance explains how to read those two threads in more detail.
Profile on the hardware that matters, in profile mode
Two testing mistakes create false confidence: profiling on your own fast phone, and judging performance in debug mode (debug Flutter is dramatically slower by design). Test in profile mode on a genuinely low-end device, the sub-$150 Android your actual users hold.
Flutter’s DevTools timeline shows exactly which frames blew the 16ms budget and which widgets were rebuilding when they did; the official performance best practices cover the full toolkit.
Older phones are also short on storage and often sit on slower connections, so app weight matters as much as frame time. I cover that side in reducing Flutter app size before you hit the store limit. The principle carries over to the web too: if you also run a WordPress site, how to speed up a WordPress site applies the same measure-first, fix-second approach.
Performance is one of several basics
Jank is the problem users feel first, but it is rarely the only shortcut a deadline creates. Apps that talk to a backend should get the same scrutiny on the server side, and the authentication mistakes found in small SaaS apps are the ones I check next.
Budget-phone performance is a discipline, not a patch. It’s why I keep an aging test device in the drawer for every Flutter build I ship. The app store rating doesn’t come from how the app runs on the developer’s phone; it comes from how it runs on everyone else’s.




