Arrow icon
From Good to Great: A Flutter Developer's Guide to Performance Optimization

From Good to Great: A Flutter Developer's Guide to Performance Optimization

Unleash the full potential of your startup's app with our comprehensive guide on Flutter performance optimization. Learn key tips and best practices to enhance speed, smoothness, and user experience.

Saiful Islam Adar

Senior Frontend Developer

August 3, 2023
Technology
Table of content

In the fast-paced world of startups, the performance of your application can be a game-changer. Flutter, Google's versatile cross-platform UI toolkit, offers a suite of robust features that have quickly gained traction among developers worldwide. However, to truly unlock Flutter's potential and ensure your application runs efficiently and smoothly, performance optimization is crucial. Let's explore some essential tips and best practices for optimizing performance in Flutter.

Minimizing Build Methods

At the heart of Flutter is a simple rule: everything is a widget. Flutter calls the build method each time it needs to render a widget. By minimizing the use of these build methods, particularly within the deeper layers of the widget tree, you can significantly enhance your application's performance.

Consider a widget that updates every second. If this widget is part of a larger parent widget, the entire parent widget would rebuild every second, even if the rest of the UI does not change. Here's how to avoid this:

Bad Practice:


Widget build(BuildContext context) {
  return Column(
    children: [
      Text('This is static text'),
      Text('Time: ${DateTime.now().second}'),
    ],
  );
}

Better Practice:


Widget build(BuildContext context) {
  return Column(
    children: [
      Text('This is static text'),
      Builder(
        builder: (BuildContext context) {
          return Text('Time: ${DateTime.now().second}');
        },
      ),
    ],
  );
}

Avoid Using Global Keys Where Possible

Global keys in Flutter can be handy for preserving state or accessing information about another widget in a different part of the widget tree. However, they can also slow down the app since they bypass the normal parent-child relationship. Use them sparingly, and only when other state management techniques are not feasible.

Global keys should only be used when absolutely necessary. For instance, to access the state of a Form widget:



/// Declare the global key
final _formKey = GlobalKey();

@override
Widget build(BuildContext context) {
  // Use the global key in the Form widget
  return Form(
    key: _formKey,
    child: // ...your fields here...
  );
}

// You can then validate the form with _formKey.currentState.validate()

Leveraging const Constructors

Using const constructors whenever possible is a smart way to optimize Flutter performance. When you use const to create a widget, Flutter doesn't need to call the build method for that widget again; it reuses the previously built instance instead. This approach minimizes CPU usage and speeds up the app's rendering process.

For example:

Without const:


@@override
Widget build(BuildContext context) {
  return Container(  // This instance will be discarded and rebuilt every time
    child: Text('Hello'),
  );
}


Use const constructors whenever possible:


@@override
Widget build(BuildContext context) {
  return const Container(  // This instance will be reused
    child: Text('Hello'),
  );
}

Image Optimization

Images can significantly impact Flutter performance, especially when dealing with large files. To optimize, ensure that images are appropriately sized for the screens where they will be displayed. Consider using plugins like flutter_image_compress for image compression and cached_network_image for caching network images.

For image compression:


iimport 'package:flutter_image_compress/flutter_image_compress.dart';

Future> compressImage(File file) async {
  var result = await FlutterImageCompress.compressWithFile(
    file.absolute.path,
    minWidth: 2300,
    minHeight: 1500,
    quality: 94,
  );
  return result;
}

For caching network images:


imimport 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: "http://via.placeholder.com/350x150",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
);

Avoiding Unnecessary Layouts

Each widget in Flutter contributes to the final layout. Hence, reducing the number of widgets can help to boost performance. Try to avoid using unnecessary parent layouts like Container, Padding, Center if they don't contribute anything meaningful to the UI.

Avoid:


CContainer(
  child: Padding(
    padding: EdgeInsets.all(10.0),
    child: Text('Hello'),
  ),
);


Instead, combine the two:


CoContainer(
  padding: EdgeInsets.all(10.0),
  child: Text('Hello'),
);

Profiling and Benchmarking

Flutter's built-in tools for profiling and benchmarking are critical for identifying performance issues. The Flutter Performance view in Android Studio and Visual Studio Code can help spot parts of your app that are causing performance issues. Use --profile mode while building your app for performance analysis and debugging.

Learn more regarding profiling from this link.

Optimizing List Views

For applications that use lists, consider using ListView.builder instead of ListView. The ListView.builder creates items on-the-fly and only for those currently visible on the screen, making it significantly more efficient for large lists.

Consider the following list of items:


final items = List.generate(10000, (i) => "Item $i");

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


Here, ListView.builder only builds widgets that are currently visible on screen, which is a more efficient approach compared to ListView, which would attempt to build all 10,000 items at once.

Deferring Initialization

Deferring the initialization of objects until they're actually needed, a practice known as lazy loading, can improve your app's startup time. This approach is especially helpful when dealing with large objects or making network calls.


Suppose you have a large list that you need to initialize. Instead of initializing it immediately, you could defer initialization until it's needed:


LList _largeList;

void _initializeLargeList() {
  _largeList = List.generate(1000000, (i) => 'Item $i');
}

void _handleOnPressed() {
  if (_largeList == null) {
    _initializeLargeList();
  }
  // Do something with _largeList
}

// In this case, _largeList will not be initialized until _handleOnPressed is called, potentially improving your app's startup time.

Conclusion

By embracing these optimization tips and best practices, you're not just improving your app's speed and smoothness—you're paving the way for a superior user experience that could set your startup apart from the competition. Remember, the journey to high-performance Flutter development begins with a performance-conscious mindset. With regular profiling and mindful coding, you're well on your way to creating applications that are as performant as they are beautiful. You are also encouraged to check out Flutter’s officialdocumentation on best practices here 

Game Changers Unite

We are the software development agency and we love helping mission-driven startups in turning their ideas into amazing products with minimal effort and time.

LET'S TALK