Flutter Best Practices (Part 2)

Pooja Patil
5 min readDec 8, 2021

Specify types for class members/variables

Always specify the type of member when its value type is known and avoid using var when possible, as var is a dynamic type that takes more space and time to resolve.

//Don't
var user= User();
var timeOut=1000;

//Do
User user= User();
double timeOut=1000;

Don’t explicitly initialize variables null

Adding redundant and unnecessary code is a bad practice in any language. If a variable has a non-nullable type, Dart reports a compile error if you try to use it before it has been definitely initialized. If the variable is nullable, then it is implicitly initialized to null for you. There’s no concept of “uninitialized memory” in Dart and no need to explicitly initialize a variable to null to be “safe”.

//Don't
int _count = null;

//Do
int _count;

Controlling build() cost

Avoid repetitive and costly work in build() methods since build() can be invoked frequently when ancestor Widgets rebuild.

//Don't
class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeaderWidget(),
_buildBodyWidget(context),
_buildFooterWidget(),
],
),
);
}

Widget _buildHeaderWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}

Widget _buildBodyWidget(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Flutter Best Practices',
),
),
),
);
}

Widget _buildFooterWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}

The downside of this approach is that when MyAwesomeWidget needs to rebuild again — which might happen frequently — all of the widgets created within the methods will also be rebuilt, leading to wasted CPU cycles and possibly memory.

Hence, it’s better to convert those methods to StatelessWidgets in the following way:

//Do
class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
HeaderWidget(),
BodyWidget(),
FooterWidget(),
],
),
);
}
}

class HeaderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
}

class BodyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Flutter Best Practices',
),
),
),
);
}
}

class FooterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}

DON’T use const redundantly.

In contexts where an expression must be constant, the const keyword is implicit, doesn’t need to be written, and shouldn’t. Those contexts are any expression inside:

  • A const collection literal.
  • A const constructor call
  • A metadata annotation.
  • The initializer for a const variable declaration.
  • A switch case expression — the part right after case before the :, not the body of the case.

(Default values are not included in this list because future versions of Dart may support non-const default values.)

Basically, any place where it would be an error to write new instead of const, Dart 2 allows you to omit the const.

//Don't
const primaryColors = const [
const Color('red', const [255, 0, 0]),
const Color('green', const [0, 255, 0]),
const Color('blue', const [0, 0, 255]),
];
//Do
const primaryColors = [
Color('red', [255, 0, 0]),
Color('green', [0, 255, 0]),
Color('blue', [0, 0, 255]),
];

DO use adjacent strings to concatenate string literals.

If you have two string literals — not values, but the actual quoted literal form — you do not need to use + to concatenate them. Just like in C and C++, simply placing them next to each other does it. This is a good way to make a single long string that doesn’t fit on one line.

//Don't
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
'parts are overrun by martians. Unclear which are which.');
//Do
raiseAlarm(
'ERROR: Parts of the spaceship are on fire. Other '
'parts are overrun by martians. Unclear which are which.');

Apply effects only when needed

Use effects carefully, as they can be expensive. Some of them invoke saveLayer() behind the scenes, which can be an expensive operation.

Why is savelayer expensive?Calling saveLayer() allocates an offscreen buffer. Drawing content into the offscreen buffer might trigger render target switches that are particularly slow in older GPUs.

Some general rules when applying specific effects:

  • Use the Opacity widget only when necessary. Applyopacity directly to an image, which is faster than using the Opacity widget.
  • Clipping doesn’t call saveLayer() (unless explicitly requested with Clip.antiAliasWithSaveLayer) so these operations aren’t as expensive as Opacity, but clipping is still costly, so use with caution. By default, clipping is disabled (Clip.none), so you must explicitly enable it when needed.

Other widgets that might trigger saveLayer() and are potentially costly:

  • ShaderMask
  • ColorFilter
  • Chip—might cause the call to saveLayer() if disabledColorAlpha != 0xff
  • Text—might cause the call to saveLayer() if there’s an overflowShader

Ways to avoid calls to saveLayer():

  • To implement fading in an image, consider using the FadeInImage widget, which applies a gradual opacity using the GPU’s fragment shader. For more information, see the Opacity docs.
  • To create a rectangle with rounded corners, instead of applying a clipping rectangle, consider using the borderRadius property offered by many of the widget classes.

Build and display frames in 16ms

Since there are two separate threads for building and rendering, you have 16ms for building and 16ms for rendering on a 60Hz display. If latency is a concern, build and display a frame in 16ms or less. Note that means built-in 8ms or less, and rendered in 8ms or less, for a total of 16ms or less. If missing frames (jankiness) is a concern, then 16ms for each of the build and render stages is OK.

If your frames are rendering in well under 16ms total in profile mode, you likely don’t have to worry about performance even if some performance pitfalls apply, but you should still aim to build and render a frame as fast as possible. Why?

Lowering the frame render time below 16ms might not make a visual difference, but it improves battery life and thermal issues.

It might run fine on your device, but consider performance for the lowest device you are targeting.

When 120fps devices become widely available, you’ll want to render frames in under 8ms (total) in order to provide the smoothest experience.

Referred Links :

Don’t explicitly initialize variables null

Controlling build() cost

Apply effects only when needed

--

--