296

For various reasons, sometimes the build method of my widgets is called again.

I know that it happens because a parent updated. But this causes undesired effects. A typical situation where it causes problems is when using FutureBuilder this way:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

In this example, if the build method were to be called again, it would trigger another HTTP request. Which is undesired.

Considering this, how to deal with the unwanted build? Is there any way to prevent a build call?

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 3
    this post may help you.. [https://stackoverflow.com/questions/53223469/flutter-statelesswidget-build-called-multiple-times/55626839#55626839](https://stackoverflow.com/questions/53223469/flutter-statelesswidget-build-called-multiple-times/55626839#55626839) – bunny Apr 11 '19 at 14:28
  • 41
    In the [provider documentation](https://pub.dev/documentation/provider/latest/) you link here saying "See this stackoverflow answer which explains in further details why using the .value constructor to create values is undesired." However, you don't mention the value constructor here or in your answer. Did you mean to link somewhere else? – Suragch May 18 '20 at 01:23
  • 4
    @Suragch this is the correct link. The problem is not specific to provider, and the issue with the ".value" constructor is identical to what is described here. That is, replace FutureBuilder with SomeProvider.value – Rémi Rousselet Aug 23 '20 at 18:19
  • 48
    I'd recommend either explaining the undesirable side effects directly in the documentation (first choice) or adding more explanation here (second choice). I don't know if I'm representative of the average Provider user or not, but when I come here I still don't understand the relationship between using `.value` and unwanted widget build or the `build` method needing to be pure. – Suragch Aug 24 '20 at 00:11
  • Just simply initialize a Future as a final variable in the State class and outside of the build() method, because State is persistent and not rebuilt, only the build() method is invoked many times. If a StatelessWidget is used, in the event where this widget is rebuilt, that would re-initialize the Future and make our FutureBuilder re-enter the loading state, which is undesired. – Son Nguyen Aug 17 '21 at 04:03
  • 8
    @Suragch I also find [that part](https://pub.dev/packages/provider#exposing-a-value) of the provider documentation very confusing. Far clearer explanation can be found on [Flutter by Example](https://flutterbyexample.com/lesson/using-value-constructors). – Ondra Simek Oct 20 '21 at 21:42
  • Can @RémiRousselet or anybody else make a more specific explanations on the side effects of using the .value constructor to create values ? The most voted answer seems has nothing to do with Provider – ximmyxiao Mar 31 '23 at 04:31

6 Answers6

400

The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:

  • Route pop/push
  • Screen resize, usually due to keyboard appearance or orientation change
  • The parent widget recreated its child
  • An InheritedWidget the widget depends on (Class.of(context) pattern) change

This means that the build method should not trigger an http call or modify any state.


How is this related to the question?

The problem you are facing is that your build method has side effects/is not pure, making extraneous build calls troublesome.

Instead of preventing build calls, you should make your build method pure, so that it can be called anytime without impact.

In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    super.initState();
    future = Future.value(42);
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

I know this already. I came here because I really want to optimize rebuilds

It is also possible to make a widget capable of rebuilding without forcing its children to build too.

When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.

The easiest way is to use dart const constructors:

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Thanks to that const keyword, the instance of DecoratedBox will stay the same even if the build was called hundreds of times.

But you can achieve the same result manually:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column does. It happens because, thanks to the closure, the instance of MyWidget didn't change.

This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.

You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.

fpsColton
  • 873
  • 5
  • 11
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 5
    Could you explain why storing `subtree` in a class field breaks hot-reload? – Michel Feinstein Mar 14 '19 at 04:27
  • 10
    A problem I am having with `StreamBuilder` is that when the keyboard appears the screen changes, so routes have to be rebuilt. So `StreamBuilder` is rebuilt and a new `StreamBuilder` is created and it subscribes to the `stream`. When a `StreamBuilder` subscribes to a `stream`, `snapshot.connectionState` becomes `ConnectionState.waiting` which makes my code return a `CircularProgressIndicator`, and then `snapshot.connectionState` changes when there's data, and my code will return a different widget, which makes the screen flicker with different stuff. – Michel Feinstein Mar 14 '19 at 04:56
  • Is placing the `StreamBuilder` in an outside variable the only solution? – Michel Feinstein Mar 14 '19 at 04:56
  • 3
    I decided to make a `StatefulWidget`, subscribe to the `stream` on `initState()` and set the `currentWidget` with `setState()` as the `stream` sends new data, passing `currentWidget` to the `build()` method. Is it there a better solution? – Michel Feinstein Mar 14 '19 at 05:33
  • 6
    I am a little bit confused. You are answering your own question, however from the content, it doesn't look like it. – sgon00 Mar 15 '19 at 15:58
  • @mFeinstein you could use Bloc pattern to expose the stream as a behavioursubject, caching last value reusing for next subscription – Vairavan Mar 18 '19 at 13:05
  • sgon00 I was questioning, but then some time later I found an answer by myself, I am just not sure it's the best. @Vairavan the problem is when there's no value sent yet and there's no seed value for a BehaviorSubject that makes sense. For example, if you want to control if there's a user signed in or not, show a Sign In screen if there's no user, the Main Screen if there's a user and a Loading screen if we are still loading the information if there's a user or not, which is what snapshot.connectionState information does. – Michel Feinstein Mar 19 '19 at 14:24
  • @Vairavan but even with a BevaiourSubject, the StreamBuilder would trigger a single "waiting" frame on widget rebuild. Stuck on this as well, any help appreciated. – Brixto Mar 20 '19 at 13:26
  • @Brixto Can you share a project via github? – Vairavan Mar 20 '19 at 13:54
  • 22
    Uh, saying that a build should not call an HTTP method completely defeats the very practical example of a `FutureBuilder`. – TheGeekZn Jun 17 '19 at 12:28
  • The nature of Stateful widgets is that, managing states, and http calls are asynchronous, so might have multiple states. Or you create wrap your http call in some sort of cache, to avoid making the call on a future rebuild... or use the initState() to store the future value... you will still make use of the FutureBuilder, unless you wanna manage the Future urself and call setState() later. This is a very good point to avoid unnecessary rebuilds and keep logic separated from UI. – roipeker Jun 29 '19 at 02:29
  • 1
    Ok, but let's say I have 3 tabs. If I go from the first to the third tab, the second tab build method is called followed by the dispose. But if the build was waiting for a response (let's say it's a ChangeNotifierProvider) from a model, it will throw an error when notifyListeners() is called and the response arrives to the second tab (A 'model' was used after being disposed. Once you have called dispose() on a 'model', it can no longer be used.) – Dpedrinha Sep 13 '19 at 21:56
  • So what if you want to have a dialog show up on error and then a button in the dialog which redirects to some other page? How can you do that? – Chrillewoodz Dec 20 '19 at 09:45
  • Anyone reading the above solution. Should also see a better approach here(deal with FutureBuilder refiring): https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2 – sagar suri Apr 27 '20 at 14:30
  • I read the article above, its definitely not better. Its an alternative, and I would say just introduces unnecessary complexity: using AsyncMemoizer. And, what happens when your Future function is more complicated? You need to create an AsyncMemoizer for each function you call in the function generating the future. – Ben Butterworth May 21 '20 at 19:10
  • @MichelFeinstein I have the same problem and it's exasperating. I decided to make a BehaviorSubjectBuilder on top of an RxDart BehaviorSubject which for this purpose is a stream whose last value you can fetch. It just wraps a StreamBuilder but the initialData is subject.value and the stream is subject.stream. I think you can make it better still by making the stream-built widget take a key so it doesn't unnecessarily get replaced by an exact copy, but that's just optimization. – Ruben Sep 22 '20 at 09:50
  • please, update this answer with null-safety – Vit Amin May 28 '21 at 06:33
  • You should move the call to `super.initState();` to the beginning of your override in the example. – fpsColton May 31 '23 at 16:44
36

You can prevent unwanted build calling, using these way

  1. Create child Statefull class for individual small part of UI

  2. Use Provider library, so using it you can stop unwanted build method calling

In these below situation build method call

  • After calling initState
  • After calling didUpdateWidget
  • when setState() is called.
  • when keyboard is open
  • when screen orientation changed
  • If Parent widget is build then child widget also rebuild
Sanjayrajsinh
  • 15,014
  • 7
  • 73
  • 78
  • 1
    the first point interferes with the last one "Create child Statefull class for individual small part of UI" with "Parent widget is build then child widget also rebuild" – Ezzabuzaid Dec 12 '20 at 23:49
  • 1
    No, Let me give example 1st one is If you have register form screen and create small child ui for getting BDay so when you rebuild BDay widget then whole registration form screen is not rebuild But if you rebuild parent screen then whole child also is rebuild – Sanjayrajsinh Dec 14 '20 at 06:39
  • 3
    if someone still wonders, @Sanjayrajsinh means that you should create small separate stateful widgets, because updating state within those won't affect the parent. If you have e.g. huge widgets each setState() will update everything – eja Dec 20 '20 at 19:49
  • 2
    when keyboard is open, everything is rebuild .. – Good Day Feb 26 '22 at 09:33
20

Flutter also has ValueListenableBuilder<T> class . It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.

you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
Taba
  • 3,850
  • 4
  • 36
  • 51
11

One of the easiest ways to avoid unwanted reBuilds that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.
For example in following code, Build method of parent page is called over and over by pressing the FAB button:

import 'package:flutter/material.dart';

void main() {
  runApp(TestApp());
}

class TestApp extends StatefulWidget {

  @override
  _TestAppState createState() => _TestAppState();
}

class _TestAppState extends State<TestApp> {

  int c = 0;

  @override
  Widget build(BuildContext context) {

    print('build is called');

    return MaterialApp(home: Scaffold(
      appBar: AppBar(
        title: Text('my test app'),
      ),
      body: Center(child:Text('this is a test page')),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            c++;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
      )
    ));
  }
}

But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:

import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';

void main() {
  runApp(TestApp());
}

class TestApp extends StatefulWidget {

  @override
  _TestAppState createState() => _TestAppState();
}

class _TestAppState extends State<TestApp> {

  int c = 0;

  @override
  Widget build(BuildContext context) {

    print('build is called');

    return MaterialApp(home: Scaffold(
      appBar: AppBar(
        title: Text('my test app'),
      ),
      body: Center(child:Text('this is a test page')),
      floatingActionButton: MyWidget(number: c)
    ));
  }
}

and the MyWidget class:

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {

  int number;
  MyWidget({this.number});

  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
        onPressed: (){
          setState(() {
            widget.number++;
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
    );
  }
}

Mohsen Emami
  • 2,709
  • 3
  • 33
  • 40
  • 2
    This is a useful idea however I believe that TestApp can then become a StatelessWidget since is has no mutable state. 'c' is declared but never changes in the second example. – greg7gkb May 01 '22 at 18:10
8

I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for

  • Route pop/push

So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page

  1. Use Navigator.pushReplacement() for navigating from the first page to Second
  2. In second page again we need to use Navigator.pushReplacement() In appBar we add -
    leading: IconButton(
            icon: Icon(Icons.arrow_back),
            onPressed: () {
              Navigator.pushReplacement(
                context,
                RightToLeft(page: MyHomePage()),
              );
            },
          )
    

In this way we can optimize our app

Rohan
  • 640
  • 7
  • 11
5

You can do something like this:

  class Example extends StatefulWidget {
      @override
      _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
      Future<int> future;
    
      @override
      void initState() {
        future = httpCall();
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: future,
          builder: (context, snapshot) {
            // create some layout here
          },
        );
      }
    
    
     void refresh(){
      setState((){
       future = httpCall();
       });
    }

  }
Cícero Moura
  • 2,027
  • 1
  • 24
  • 36