Flutter Performance Optimization Tips

Ashwin Mali
6 min readMar 3, 2023

Avoid State Flutter Widgets

→ The common mistake we all make is using State Flutter widgets for Flutter App development at the beginning of development. Stateful widgets can be used if your application has a large build function and you want to rebuild.

→ SetState() and StatefulWidget should only be used to rebuild or update. Moreover, it is better to avoid using it in whole widgets for better Flutter performance.

Use Const Keyword

Const keyword works as a constant, which is a type of Flutter widget used at a time of compilation to avoid. Const allows using multiple widgets without reducing performance. Another benefit of using const is that it avoids rebuilding whenever you use different widgets.

Ex.

const EdgeInsets.fromLTRB(16, 4, 16, 4);
const Color black = Color(0xFF000000);
const Text('This is a static text')

Try Using Async/Await

→ When writing your execution flow, it is important to determine whether the code is allowed to run synchronously or asynchronously. Async code is more difficult to debug and improve, but there are still a few ways you can write async code in Flutter, which includes using Future, async/await, and others.

→ When combined with async, the code readability improves because the structure and pattern of writing code are followed. On the other hand, overall execution performance improves due to its ability to entertain fail-safe strategies where needed — in this case, try … catch. Let’s look at the example below:

Future<int> countCarsInParkingLot() {
return getTotalCars().then((cars) {
return cars?.length ?? 0;
}).catchError((e) {
log.error(e);
return 0;
});
}

// Appropriate
Future<int> countCarsInParkingLot() async { // use of async
try {
var cars = await getTotalCars();

return cars?.length ?? 0;

} catch (e) {
log.error(e);
return 0;
}
}

Efficiently use operators

→ Flutter is packed with language-specific features. One of them is operators.

→ Null-check operators, nullable operators, and other appropriate ones are recommended if you want to reduce development time, write robust code to avoid logical errors, and also improve the readability of the code.

→ Let’s look at some examples below:

car = van == null ? bus : audi; // Old pattern
car = audi ?? bus; // New pattern
car = van == null ? null : audi.bus; // Old pattern
car = audi?.bus; // New pattern
(item as Car).name = 'Mustang'; // Old pattern
if (item is Car) item.name = 'Mustang'; // New pattern

Make use of interpolation techniques

→ It is a common practice to perform string operations and chaining using the operator +. Instead of doing that, we’ll make use of string interpolation, which improves the readability of your code and reduces the chances of errors.

// Inappropriate
var discountText = 'Hello, ' + name + '! You have won a brand new ' + brand.name + 'voucher! Please enter your email to redeem. The offer expires within ' + timeRemaining.toString() ' minutes.';
// Appropriate
var discountText = 'Hello, $name! You have won a brand new ${brand.name} voucher! Please enter your email to redeem. The offer expires within ${timeRemaining} minutes.';

Develop And Display Frames Within 16ms

→ The display is divided into two parts: structure and picture. Developers have 8ms for structure and another 8ms for the picture to render a 60hz display.

→ Always divide 16ms equally between structure and picture for better flutter performance in your application.

→ You must be wondering about the 16ms will decrease the display quality? Don’t worry; 16ms will not affect the quality of the display. It is going to improve the battery life of the system. Moreover, with 16ms, you can get better performance on smaller devices.

Split Large widgets into smaller widgets.

→ Similar to tip number 1 you should try to minimize the size of your build function and keep widgets as small and modular as possible. The reasoning behind this tip is similar to the reasoning behind tip number 1. Stateful widgets are being redrawn when there is a state change, which means that the entire widget will have to be rebuild.

  • The larger the build tree, the more resources it takes to be rebuilt.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
_getIcon(),
InkWell(
onTap: () {
setState(() {
count += 1;
});
},
child: Text(
count.toString(),
)),
_getIcon()
], ),);}

Widget _getIcon() {
return IconButton(
icon: Icon(Icons.search),
onPressed: () {
Navigator.of(context).pushNamed('productSearch');
});
}
}

Avoid using opacity as much as possible.

→ Avoid using the Opacity widget as much as possible. Many people might use the Opacity widget to hide a certain widget, which is common in other programming language such as Objective-C. For example, if you want to hide a widget you can simply wrap it into an Opacity widget.

Opacity(
opacity: _visible ? 1.0 : 0.0,
child: Text("Text!"),
)

→ While this works, it is expensive to keep your widget hidden on the screen. Instead, try rebuilding your widget in a way that it reconstructs without the Text widget. If you want the widget to take space on the screen, you can use the Visibility widget which is more efficient than Opacity because it has only two states visible/invisible (it doesn’t work with fractional opacity values).

Ignore Rebuilding of Widget in AnimatedBuilder.

→ Animation is one of the most attractive features in any web or mobile application. It grabs the user’s attention, but at the same time, it decreases the performance of the application.

→ Developers generally use AnimationController. However, it rebuilds multiple widgets in AnimatedBuilder, and that’s the common reason behind the slow Flutter performance.

→ To avoid bad performance issues, you can use CounterWidget, which helps develop animation without rebuilding multiple widgets.

Avoid Build Method

→ Try to avoid using the Build() method as it is costly and consumes lots of CPU power. Repetitive use of Build() can decrease the Flutter performance. To get the best Flutter performance in your existing application, you can divide the large widgets developed with the Build() method into smaller ones.

Load list items efficiently — and on-demand

→ When working with list items, developers generally use a combination of the widgets SingleChildScrollView and Column.

→ When working with large lists, things can get messy pretty quickly if you continue using this same set of widgets. This is because each item is attached to the list and then rendered on the screen, which increases the overall load on the system.

→ It is a good idea to use the ListView builder in such cases. This improves performance on a very high level. Let’s look at an example for a builder object:

ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Row: ${items[index]}'),
);},);

Decrease Application Size

→ It is really easy to add a ton of packages to your code during your development process. As you’re probably aware, this can turn into bloatware.

→ Let’s use an Android app as an example. You can use Gradle, a powerful open-source build tool which comes with a plethora of configuration options, to reduce the app’s size.

→ You can also generate Android app bundles, which are a new packaging system introduced by Google.

→ App bundles are efficient in multiple ways. Only the code necessary for a specific target device is downloaded from the Google Play Store. This is made possible as the Google Play Store repacks and ships only the necessary files and resources for the target device’s screen density, platform architecture, supporting hardware features, and so on.

→ Google Play Console Stats show that the download size of the app is reduced by 40 to 60 percent in most cases when you choose app bundles over APKs.

→ The command to generate an app bundle is:

flutter build appbundle

→ To obfuscate the Dart language code, you need to use obfuscate and the — split-debug-info flag with the build command. The command looks like this:

flutter build apk - obfuscate - split-debug-info=/<project-name>/<directory>

The above command generates a symbol mapping file. This file is useful to de-obfuscate stack traces.

--

--