I have a Flutter app which is becoming more and more janky as time goes by and more features are added. Therefore, is there some utility to make it as smooth as 60FPS?
I know there are some official guides here: https://docs.flutter.dev/perf. However, I have tried to optimize and it is still slow. You know, some things just cannot be fast enough, such as long text, dynamic layout, necessary synchronous computations, etc. Especially when entering a new page or scrolling down a ListView. In addition, I have to use brainpower to find out what is slow and optimize when new features are added, so I hope there is some fully automatic thing which I can drop-in replace and forget it and it just works forever.
Disclaimer: I wrote this package and this is a Q&A style StackOverflow answer.
Yes, I have made it: https://github.com/fzyzcjy/flutter_smooth.
No matter how heavy the tree is to build/layout, it will run at (roughly) full FPS, feel smooth, has zero uncomfortable janks, with neglitable overhead. (I have made some benchmark reports here)
As for usage, for common scenarios, add 6 characters ("Smooth") - ListView becomes SmoothListView, MaterialPageRoute becomes SmoothMaterialPageRoute. For complex cases, use SmoothBuilder(builder: ...) and put whatever you want to be smooth inside the builder.
Roughly speaking about the implementation, it is done by submitting extra frames to the rasterizer every ~16ms, without disturbing all existing code. Therefore, the existing app code will almost not even know the existence of this package.
You need to check how do you use widgets, unnecessary rebuilds, some heavy operations when the widget is creating o rebuilding and it’s recommended to use the performance profiler in the devtools.
The more things I do, the more often I run across things that need disposing (e. g. Timers and ScrollControllers). Two questions:
Is there a way to see/know what things need to be disposed? Or is it just a thing you need to learn by heart? For example: At the moment I'm not sure, if I need to dispose of providers.
Do I need to expose things (e. g. Timers) in stateless widgets? If so how? Or do I turn a widget stateful just so I gain access to the dispose method? That seems wasteful.
Thanks!
If disposing is needed, it's usually stated in docs.
It's not about turning into a stateful widget "just to gain access to the dispose method". It's turning into a stateful widget because it has a state. If it contains a timer, it already implies having a state. Think of stateless widgets as pure functions – you can call them several times and there should be no side effects. Timer is such a side effect since you don't want it to be re-created on every stateless widget creating. Also, there's no significant performance impact on converting stateless widget to stateful.
I am using AutomaticKeepAliveClientMixin to keep widgets in my list view alive but while scrolling it feels like it is hanging a lot , i mean scrolling is not smooth and also there are duplicate widgets in list.
In debug mode, the compiler emits debug symbols for all variables and compiles the code as is. You can't judge your app performance in debug mode
In release mode, some optimizations are included:
Unused variables do not get compiled at all
Some loop variables are taken out of the loo.p by the compiler if they are proven to be invariants.
Code written in debug directive is not included, etc.
In release mode only you can judge your app performance. So, don't worry about the smoothness or the performance of the app
You should consider using Pagination if you have 1,00,000 items to be displayed in your ListView. That would help in some performance gain I believe.
I'm totally new to flutter app but have strong concept in android/kotlin. I'm trying to understand the basic structure of the flutter app. I read that every widget need a build function to override to draw the children that was fine for me because in android/kotlin there is onCreate(); or similar others. Then I saw this code on the official document page.
void main() {
runApp(
Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
It is working fine without build() function so what is the real purpose of the build function? And when we need it? What can be without it or what can't?
While you could have everything passed directly to runApp, it has a pretty big drawback:
Your app would be static. Without a build function (or a builder like with FutureBuilder), then your app will have no way of having dynamic content.
It is also pretty bad for reusability. You may want to extract some part of this widget tree into custom widgets, to reuse them in different locations – which implies a build method for that custom widget.
Flutter functions rather differently from many other platforms, and the name build() only adds to the confusion. :-)) To understand it, try to forget your previous experiences with other platforms temporarily.
Build() is really more like display()
Flutter uses the build() system to display the current frame. If the app has rapidly changing content, like an animation or a game, build() will be called 60 or more times per second, that is, for every single frame. Yes, that's the intention: no matter what its name is, think about it as a function to displayCurrentFrame(), not what the name might imply, to build a widget and then use it for the rest of the life of your app.
The system is perfectly optimized to know when it has to call build(). For content that doesn't change that often, it will not call it 60 times per second, it's smart enough not to do that. But every time it's really needed, it will be called (and for really complicated cases, you also have mechanisms to help Flutter decide when to call and what to call, to make sure that only parts that really change get redrawn, making it possible to avoid jerkyness in apps that really need rapidly changing content, mostly games).
The task of your build() is to take whatever data you currently have (that's called the state of your widget) and build up the widget (or widgets) just with that data, just for that single display frame. Next time around, with possibly different data, you will build it again and again.
So, a Flutter app works differently from many other platforms. You don't write code that waits for interaction from the user, then, for instance, calls a display function to show a new selection, a new text entry, a new image, anything directly. All your widgets function like this: whenever there is a change, the user does something, a response arrives from a call you made over the internet, a timer has elapsed, so basically, anything happens, your widget stores whatever new data you just received into its own state and tells Flutter that "hey, there are changes, please, call me so that I can draw a current version of myself." And Flutter will call its build() all right, and your widget will display itself according to this current new data. Again and again, as long as your app is alive and kicking.
At first, all this might seem like a waste of resources. Why rebuild everything on potentially every frame, instead of building a widget, keeping it alive and using it while the app lasts? The only realistic answer is: don't worry. The system was conceived explicitely with that structure in mind, it gets compiled into optimized code that works just fine, it's fast and responsive. Those widgets are lightweight enough so that this doesn't mean the slightest problem in real life (and, as already mentioned, in really complex programs where it starts to matter, you can have your say in how it should work, but you don't have to worry about that, either, until you reach that stage when it really counts). Just get used to it and accept that this is the way Flutter works. :-)
Other implications
All this has other implications as well. You should never do anything in build() that you're not comfortable doing on every display cycle: both because it shouldn't take too much time and because it will be called over and over again. Especially not anything that's a longish operation. You may start an async operation (actually, you probably do so quite often when acting on some user input), but that's async, it goes away to do its job and simply returns later. When it does return, you store the new data in the state, as described above, and use that in build() the next time around. But you never wait for anything there, or do any real complex programming logic and perform tasks there. It's nothing more than a display(), really.
I clearly understand the difference between stateful and stateless widgets. But basically if I want, I can always use some statefulwidgets even if they will not be updated. What happen if I do that? Will there be a performance issue?
Nothing bad.
You'll have a small overhead, and StatelessWidget is slightly more performant (just slightly). But overall, nothing special.