Improving the performance of your application is always important. And in the case of this slide puzzle game app, there were lots of animations and graphics, so it was vital that some work and effort be directed into making sure the performance of the app was at its best.
Aside from using const in every possible place, having smaller build methods, having minimum rebuilds to update the UI, 2 major performance optimizations were done:
This is especially important for the planets you see in the background. On the first app run, they animate in, so it’s important that their images are pre-loaded so that they don’t suddenly appear halfway through the animation.
This is done with a simple “precacheImage” function built-in in flutter. All you have to do is call this method, I’m calling it in the didChangeDependencies of my App widget, and give it the value of the ImageProvider of your asset image like the code below.
You can see the following code in the app.dart file in the source code.
bool _isInit = true;
@override
void didChangeDependencies() {
if (_isInit) {
for (BackgroundLayerType layerType in BackgroundLayers.types) {
precacheImage(
Image.asset('assets/images/background/${layerType.name}.png').image,
context,
);
}
for (int size in Puzzle.supportedPuzzleSizes) {
precacheImage(
Image.asset('assets/images/puzzle-solved/solved-${size}x$size.png').image,
context,
);
}
}
_isInit = false;
super.didChangeDependencies();
}
I’ve avoided calling this function in the initState method because the function takes a BuildContext and we want to make sure the context of our app is ready to be used, so we call it in the didChangeDependencies method that is called right after initState. I used the _isInit variable to make sure the precaching is done only once.
In the code above, I’m utilizing Dart’s enum features by using the “name” property of my BackgroundLayerType enums that I have in a list in the background_layers.dart file
static List<BackgroundLayerType> types = [
BackgroundLayerType.topBgPlanet,
BackgroundLayerType.topRightPlanet,
BackgroundLayerType.topLeftPlanet,
BackgroundLayerType.bottomLeftPlanet,
BackgroundLayerType.bottomRightPlanet,
];
I’m also precaching the images that are displayed when each of the puzzle sizes is won. And I’m using my supportedPuzzleSizes array that I defined in the puzzle.dart file for this.
static List<int> supportedPuzzleSizes = [3, 4, 5, 6];
This is the content of the images folder:
Have you ever noticed animations being “janky” when you run your app the first time, then they run smoothly? This is exactly what warming up SKSL shaders helps you avoid.
Flutter uses Skia as a graphics engine to render its visuals. Shaders are the code that runs on a GPU to display those visuals. For example, a Flutter route push/pop, app drawer slide in/out, bottom sheets, or any other UI rendering/animations. These shaders, when run on a device, need to be compiled on that device first and this compilation takes more time than the frame time required by Flutter’s 60 frames per second rendering. And then they run smoothly after they are compiled. This longer time is what causes the jank that you see only on first runs.
Flutter gives you a way to “warm up” those shaders by storing compiled shaders in a json file in the Skia Shader Language format. You can then bundle the app with this file and build it.
All you need to do is run your app in profile mode with the flag “--cache-sksl”, play with the app and trigger as many animations as possible when the app is running, and then hit M. Flutter then writes a json file that you can bundle in your next app build.
The command for running the app:
flutter run --profile --cache-sksl --purge-persistent-cache
(Add the --purge-persistent-cache flag only if you ran the app without caching SKSL before)
The commands for building the app with the generated json file:
flutter build apk --bundle-sksl-path flutter_01.sksl.json
flutter build appbundle --bundle-sksl-path flutter_01.sksl.json
flutter build ios --bundle-sksl-path flutter_01.sksl.json
Please note that for better performance, you should do the above for each platform separately (Android/iOS) and bundle the corresponding json file to ensure platform-specific shaders are warmed up.
Resources: