77

When creating a widget tree, will inserting const before static widgets improve performance?

ie

child: const Text('This is some text');

vs

child: Text('This is some text');

I know that, with Dart 2, const is optional and will be inserted automatically in some places. Is this one of those situations? If it isn't, will using const reduce memory usage/improve performance?

maxmitz
  • 258
  • 1
  • 4
  • 17
Noah Klayman
  • 770
  • 1
  • 5
  • 6

4 Answers4

56

It is a small performance improvement, but it can add up in larger apps or apps where the view is rebuilt often for example because of animations.
const reduces the required work for the Garbage Collector.

You can enable some linter rules in analysis_options.yaml that tell you when you should add const because it's not inferred but would be possible like

or that reminds you when you use const but it is inferred anyway

See also https://www.dartlang.org/guides/language/analysis-options

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Do you have a source for the performance gains you claim you are getting in large apps ? – Ced Jul 09 '20 at 16:12
  • 3
    No, you would need to do the benchmarks yourself. This can also change depending on whether you run it in the VM (Flutter debug mode, console app), compiled to JS, or in Flutter release mode. Const values are canonicalized and there is only ever one instance of a class with a specific set of constructor parameters in memory. No matter how many such unique values you create Dart uses always the same instance and the garbage collector never collects it. So it's quite likely that it improves performance, especially with static Flutter widgets which are recreated quite frequently. – Günter Zöchbauer Jul 09 '20 at 17:15
  • I answered the question with "tests" – Ced Jul 09 '20 at 23:45
41

In the case of Flutter, the real gain with const is not having less instantiation. Flutter has a special treatment for when the instance of a widget doesn't change: it doesn't rebuild them.

Consider the following:

Foo(
  child: const Bar(
    child: Baz() 
  ),
)

In the case of build method being called again (setState, parent rebuild, Inheritedwidget...), then due to the const for Bar subtree, only Foo will see its build method called.

Bar will never get rebuilt because of its parent, because Flutter knows that since the widget instance didn't change, there's nothing to update.

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 6
    It doesn't need to be const for that to work. If the same instance is returned it will have the same effect. With const it's just automatically always the same instance. – Günter Zöchbauer Nov 27 '18 at 08:42
  • 3
    Indeed, but the most common use of that optimization is using `const` (or with `child`/`children`) – Rémi Rousselet Nov 27 '18 at 08:45
  • @GünterZöchbauer but there will never be two same instances of the widget unless you override its `operator==` and `hashcode`, right? – Kirill Karmazin Jan 05 '23 at 15:05
  • @KirillKarmazin I don't really understand your comment. You have to differentiate. `const` works at compile time, this might reduce CPU usage at runtime. Flutter handles widgets at runtime. There might be all kinds of mechanisms at work that influence its behavior or performance, like object equality. In the end you need to be aware that performance discussions are usually just general theory unless a benchmark for your concrete situation shows that there actually is a performance problem and what changes affect it positively or negatively. – Günter Zöchbauer Jan 06 '23 at 11:22
16

Update: I noticed I received an upvote lately, and I must say that I'm not confident in my tests below but that's all I got. So someone better run a better test.


I've ran some test to see if it makes a difference.

The tests are heavily based on the ones done in this article.

For the tests, there are 300 containers with text inside moving randomly on the screen. Something you wouldn't see in a day to day app.

For my results there is no difference in frame per second and there is no difference in memory usage except that the Garbage collector seems to run more often when not using const. Again, the FPS were about the same.

image of memory usage

Imo, the performance boost is negligible and sounds like preemptive optimization. However there is no deeply nested chain of widgets in the test, but I don't see how that would make a difference. The article above seems to say there is a small one, but that's not what my tests show.

I've a card like this (this is the const version):

import 'package:flutter/material.dart';

class MyCard extends StatelessWidget {
  const MyCard();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Container(
        margin: const EdgeInsets.all(8.0),
        height: 100,
        width: 100,
        color: Colors.red,
        child: const Text('Hi'),
      ),
    );
  }
}

that's rendered 300 times and moving on the screen randomly.

This is the widget that makes them move

import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:async';
import './my-card.dart';

class MovingContainer extends StatefulWidget {
  @override
  _MovingContainerState createState() => _MovingContainerState();
}

class _MovingContainerState extends State<MovingContainer> {
  final Random _random = Random();
  final Duration _duration = const Duration(milliseconds: 1000);
  Timer _timer;
  double _top = 0;
  double _left = 0;

  @override
  void initState() {
    super.initState();
    initMove();
  }

  void initMove() {
    _timer = Timer.periodic(
      _duration,
      (timer) {
        move();
      },
    );
  }
  void move() {
    final Size size = MediaQuery.of(context).size;
    setState(() {
      _top = _random.nextInt(size.height.toInt() - 100).toDouble();
      _left = _random.nextInt(size.width.toInt() - 100).toDouble();
    });
  }

  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedPositioned(
      top: _top,
      left: _left,
      child: const MyCard(),
      duration: _duration,
    );
  }
}

Note: I'm new to flutter, and so are many others because it's a relatively new framework. Therefor my tests could very well be wrong, don't take it as gospel. Also don't take it as gospel when you read an article titled << Number One Perf gain on Flutter >>. I've yet to see actual proof there is a perf gain.

Ced
  • 15,847
  • 14
  • 87
  • 146
  • 2
    That shows that it's always important to do your own benchmarks :) I wouldn't agree "dozens" of widgets. In a material design app the widget tree is so large that it's quite cumbersome because there are hundreds of widgets. Still not a huge number (thousands). I'd first try to build an application that brings the device to its limits and then check if using const makes a difference. As long as the device has enough power to do GC without causing lags, it doesn't matter anyway. – Günter Zöchbauer Jul 10 '20 at 06:21
  • 1
    I don't think this tests what const actually does, which is preventing un-needed builds. If you have 100 items in a stack, and call setState() on the stack parent, thats going to be 100 child.build calls, unless you have some consts in the tree. A better test would be, stick 100 cards in a stack, change 1 of them each frame. The one with conts should crush... ok now I want to test it! – shawnblais Dec 03 '20 at 03:22
  • @shawnblais there is a const card though that's changed to non const for the non const test. Maybe a better test would be with a longer tree ? – Ced Oct 13 '21 at 06:15
1

When we use setState() Flutter calls the build method and rebuilds every widget tree inside it. The best way to avoid this is by using const costructors.

Use const constructors whenever possible when building your own widgets or using Flutter widgets. This helps Flutter to rebuild only widgets that should be updated.

So if you have a StatefulWidget and you are using setState((){}) to update that widget and you have widgets like:

class _MyWidgetState extends State<MyWidget> {

  String title = "Title";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: <Widget>[
          const Text("Text 1"),
          const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const Text("Another Text widget"),
          ),
          const Text("Text 3"),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() => title = 'New Title');
        },
      ),
    );
  }
}
Faiz Ahmad Dae
  • 5,653
  • 2
  • 14
  • 19