5 Tips On How to Improve the Performance of your Flutter App

Tips on how to improve the performance of your flutter app

I want to share with you a list of 5 tips to improve the performance of your flutter app that we used when we built our Premium Ecommerce Flutter template. I’m hoping these flutter performance tips can help other developers with improving the speed of their app.

1. Use stateless widgets

The first mistake that we do is that we rely too much on stateful widgets to build our app. While stateful widgets are great, if you have a large build function with a heavy and expensive widget tree calling setState() will use a lot of resources to rebuild the entire widget.

Break down heavy build functions

One solution to avoid heavy build functions is to break down large widgets into multiple widgets. The sub-widgets created should be stateless widgets whenever possible. A stateless widget needs an immutable configuration, so you should use them for parts of the user interface that does not change dynamically.

Don’t use helper functions for rendering UI

If you are coming with experience from another programming language watch out for helper functions. Helper functions are great, but not so much in flutter when you are rendering UI. For example, if you have parts of a screen in your app that repeats, instead of creating a helper private function to generate the widget try creating a stateless widget instead.

The best way to understand this issue is by looking at an example. Here is how a code that will run into performance issues looks like:

// Flutter tutorial
import 'package:flutter/material.dart';

class JDHomePage extends StatefulWidget {
  @override
  _JDHomePageState createState() => _JDHomePageState();
}

class _JDHomePageState extends State<JDHomePage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () {
              Navigator.of(context).pushNamed('productSearch');
            }),
          InkWell(
              onTap: () {
                setState(() {
                  count += 1;
                });
              },
              child: Text(
                count.toString(),
              )),
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () {
              Navigator.of(context).pushNamed('productSearch');
            })]));  }}

We can clearly see that the code from line 16 to line 21 is the same as the code from line 31 to 35. This is the code used to render a search icon that lands users on the search page. This rendering code is in a stateful widget that will be rebuilt when the user taps on the Text widget.

The first optimization that many think when they see this code is adding the duplicated code inside a helper function:

// Build method
@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');
      });
}
}

This code already looks better, we increased the readability and the reusability of the code but we still haven’t improved the performance of our build method. For this, we have to turn our _getIcon() method into a stateless widget. The stateless widgets are cached and they will not be rebuilt when setState is called.

// Build method
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          JDSearchIcon(),
          InkWell(
              onTap: () {
                setState(() {
                  count += 1;
                });
              },
              child: Text(
                count.toString(),
              )),
          JDSearchIcon(),
        ],),);}}

class JDSearchIcon extends StatelessWidget {
  const JDSearchIcon({Key? key}) : super(key: key);

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

You can place the new JDSearchIcon widget into a new function and then you will be able to reuse it across the app as a stateless component.

2. 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.

For an example, see the example in tip number 1, where we split a larger widget into two widgets.

3. Use the const keyword.

Now that I split my widgets into smaller subwidgets and used stateless widgets whenever possible, it is time to optimize the resulting widgets. One way to do that is by reducing the memory usage of my widgets.

  • Using the const keyword wherever applicable can be a great way to reduce memory usage.

Here are some common use cases where I used the const keyword:

Const with EdgeInsets, Color and Text

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

Using const in Constructors.

If you have an immutable stateless widget, you can make it a const which means Flutter will only create one instance of this widget in memory. In the example from Tip Number 1, you can see the JDSearchIcon widget has a const constructor.

Column(
  children: [
    const JDSearchIcon(),
    InkWell(....),
    const JDSearchIcon(),
  ],
),

Now that we used the right keyword in front of our stateless widgets, Flutter will only keep in memory one widget and reference it in both cases. If you have many places where you can add this optimization, it should definitely improve the performance of your flutter app.

Also, another huge advantage of using the const keyword in this example is provided by the performance gain of avoiding unnecessary builds of a widget. For example, if the parent widget of JDSearchIcon rebuilds, we will not rebuild the JDSearchIcon widgets as well.

4. Render only widgets that are visible on the screen

When you are dealing with a large vertical or horizontal list of widgets, you should avoid using a solution that creates all the widgets visible on the screen at once. Instead, try using ListView.builder to improve the performance of your flutter app.

For example, imagine we are building the Twitter app and we want to build a timeline with hundreds of tweets. You should avoid using a Column or a ListView constructor like this:

ListView(children: _getTwets()); 
// or
Column(children: _getTweets());

List<Widget> _getTweets() {
  for (var tweet in dataSource) {
    return Tweet(tweet);
  }
}

If we use the example about, we will end up building all the tweets at once when we load the app. This is highly inefficient and we should use Listview.builder:

ListView.builder(
  itemCount: dataSource.length,
  itemBuilder: (context, index) {
    return Tweet(dataSource[index]);
  },
)

5. 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).

For more details and tips on how to improve performance of your flutter app, you can check the Flutter documentation here.

Conclusion

To build a Flutter mobile app can be fast, but to build a highly performant one is not that straight forward. I hope the 5 flutter performance tips presented above will help you speed up your mobile app.

Also, if you are interested in building an Ecommerce App, you can download our Flutter Ecommerce template from here. We used all these tips in building our app.

Leave a Reply

Your email address will not be published.