0

To optimize the performance of a Flutter App it's important to only rebuild Widgets when it's actually needed to rebuild them. Between using const Widgets, the TransitionBuilder pattern, Keys and overwritten the == operator there seem to be different approaches to reduce Widget rebuilds, but I have no good idea of how the interact.

The official documentation suggests to do as little work as possible in build methods. How does that compare to work done in the constructor of the Widget? Does the constructor get called as often as the build function, more then it or less then it? If I have a Stateful Widget can I improve performance by caching some data in the state instead of in the constructor or build method?

On Reddit there's a discussion about using functions vs. Stateless Widgets where some people argue that Stateless Widgets cause less rebuilds and others that there's no difference. I would like to understand Flutter's Widget Lifecycle well enough to understand whether in a particular case there's a difference between the two or there isn't.

I'm looking for an explanations of how those different mechanisms interact to make good decisions about how to reduce unnecessary Widget builds in my app.

Christian
  • 25,249
  • 40
  • 134
  • 225

2 Answers2

1

Not every rebuild is expensive as you may think

This question bugged me a lot in the past :) until you realize that widgets is not the whole story . It is just the tip of the iceberg! . For every widget object there actually two other objects associated with it first is element the other one is called render you can consider widgets as just configuration objects when widgets destroyed and then created this in itself is not expensive what is expensive is the creation of the render object , fortunately the render objects is not reconstructed with every build until it is needed so it is compared and re associated with the new widget in a very efficient and smart way that is why flutter is so performant out of the box . Hope that help, I will be more than happy to clarify more if needed.

Saed Nabil
  • 6,705
  • 1
  • 14
  • 36
  • 1
    My question is about how the "efficient and smart way" works. Saying "it's smart" doesn't answer the question. – Christian May 05 '21 at 08:59
  • @Christian nothing so special about it it's algorithm to compare the new configuration from the new widgets and update the render accordingly if needed using skia renderer – Saed Nabil May 05 '21 at 17:12
  • Saying it's not special doesn't tell me anything about how the decision about what gets rerendered gets made. – Christian May 05 '21 at 20:31
  • @Christian I say it is smart and. not special in the context that it is another algorithm after all ,if you need to know more you can have a look here https://flutter.dev/docs/resources/inside-flutter , hope that help – Saed Nabil May 05 '21 at 21:00
1

Rather the me butchering a nice explanation from the developers themselves, here is a short clip explaining the widget lifecycle

A more detailed explanation was made in 2019 At Google Developer Days China

If you talk about performance, the functional approach is better. Consider this:

class TestWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final streamController = StreamController<String>.broadcast();

    Future.delayed(Duration(seconds: 3)).then((_) {
      streamController.sink.add('new value to received');

      streamController.close();
    });

    return StreamBuilder(
      stream: streamController.stream,
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        final newVal = snapshot.data;
        return Text(newVal ?? 'this is initial value');
      },
    );
  }
}

In a functional approach the state is held not by the widget but by the streamController, and the TestWidget's build method is called only once, and the builder block is called every time there is a new event inside the stream.

I find this approach the most flexible to develop also, since you can have multiple streams in one place and as a puppeteer control them all.

==== A small tangent =====================================================

I suggest you looking into that the BLoC pattern.

From my experience, if you fanatically follow one approach over the other you could end up "hurting yourself", so i'll suggest to not be afraid to use both, depending on the use-case.

For example if you have a button with an incrementing int value inside it (like a badge), and when you tap it increments. Sending the value to the BLoC and persisting it there, then sending an event through the stream back to update the UI is overkill, this is where i would use a StatefulWidget.

  • I do use Bloc. That doesn't answer the question. The first linked video basically says "Flutter does smart magic in the background". My question is about understanding the magic. – Christian May 05 '21 at 09:01
  • In the second clip explains more about the magic that you are talking about. – Cosneanu Cristian May 05 '21 at 09:16
  • In a nutshell: No matter what approach you use to change the state, the process of updating the `Widget`, `Element` and `RenderObject` trees are the same, from a performance standpoint. With the example above i was trying to say that with the `builder` you will instantiate less objects during a rebuild to update the `Text`. – Cosneanu Cristian May 05 '21 at 09:23
  • I question is about how all the factors I listed affect performance. I don't see anything in your answer that tells me about how const and overwritting == plays into performance and as such it's not really telling my anything I asked about that I don't know currently. – Christian May 05 '21 at 09:26
  • `I would like to understand Flutter's Widget Lifecycle well enough to understand whether in a particular case there's a difference between the two or there isn't.` Isn't this the question you were interested in? – Cosneanu Cristian May 05 '21 at 09:49
  • Yes, and your answer does nothing to help with that. – Christian May 05 '21 at 10:21
  • It does, the video links that i sent contains a clear explanation of the widget lifecycle. I misunderstood the part with `fuctions vs stateless`  sorry . This is why the second part is not accurate. Technically, from a machine code perspective, defining a widget inside a function will take more time and memory than directly declaring it as a `child`. BUT, the difference is so insignificant, at the level of a few clock ticks, current processors easily do over 1 billion ticks per second. This will NOT, in any way, affect the performance of your app. – Cosneanu Cristian May 05 '21 at 11:16
  • I basically listed 5 different cases that I don't understand well currently and I gained more knowledge about none of them. StackOverflow answers are also generally supposed to stand on their own. Why do you think a GDE like [Diego Velásquez](https://blog.codemagic.io/how-to-improve-the-performance-of-your-flutter-app./) says functions vs. stateless matters a lot and you think it doesn't? Why should I believe that you understand it better then the GDE? – Christian May 05 '21 at 11:42
  • ‍♂️ fair enough. May your questions be answered! – Cosneanu Cristian May 05 '21 at 12:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231982/discussion-between-cosneanu-cristian-and-christian). – Cosneanu Cristian May 05 '21 at 14:57