Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter: when are const widgets rebuilt?

Tags:

flutter

dart

I'm currently reading the example code of the provider package:

// ignore_for_file: public_member_api_docs
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
      ],
      child: Consumer<Counter>(
        builder: (context, counter, _) {
          return MaterialApp(
            supportedLocales: const [Locale('en')],
            localizationsDelegates: [
              DefaultMaterialLocalizations.delegate,
              DefaultWidgetsLocalizations.delegate,
              _ExampleLocalizationsDelegate(counter.count),
            ],
            home: const MyHomePage(),
          );
        },
      ),
    );
  }
}

class ExampleLocalizations {
  static ExampleLocalizations of(BuildContext context) =>
      Localizations.of<ExampleLocalizations>(context, ExampleLocalizations);

  const ExampleLocalizations(this._count);

  final int _count;

  String get title => 'Tapped $_count times';
}

class _ExampleLocalizationsDelegate
    extends LocalizationsDelegate<ExampleLocalizations> {
  const _ExampleLocalizationsDelegate(this.count);

  final int count;

  @override
  bool isSupported(Locale locale) => locale.languageCode == 'en';

  @override
  Future<ExampleLocalizations> load(Locale locale) =>
      SynchronousFuture(ExampleLocalizations(count));

  @override
  bool shouldReload(_ExampleLocalizationsDelegate old) => old.count != count;
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Title()),
      body: const Center(child: CounterLabel()),
      floatingActionButton: const IncrementCounterButton(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<Counter>(context).increment,
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '${counter.count}',
          style: Theme.of(context).textTheme.display1,
        ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Text(ExampleLocalizations.of(context).title);
  }
}

At first I was confused to see the following code. It is a MultiProvider, immediately followed by a Consumer, at the top of the Widget tree:

return MultiProvider(
  providers: [
    ChangeNotifierProvider(builder: (_)=>Counter()),
  ],
  child: Consumer<Counter>(
    builder: (context, counter, _){
      return MaterialApp(
        home: const MyHomePage()
      );
    },
  ),
);

I was wondering: Isn't this really bad for performance? Everytime the state of the consumer is updated, all the tree has to be rebuilt. Then I realized the const qualifiers everywhere. This seems like a very neat setup. I decided to debug through it and see when and where widgets are rebuilt.

When the app is first started, flutter goes down the tree and builds the widgets one by one. This makes sense.

When the button is clicked and the Counter is incremented, builder is called on the Consumer at the very top of the tree. After that, build is called on CounterLabel and IncrementCounterButton.

CounterLabel makes sense. This is not const and will actually change its content. But IncrementCounterButton is marked as const. Why does it rebuild?

It is not clear to me why some const widgets are rebuilt while others aren't. What is the system behind this?

like image 860
lhk Avatar asked Jun 19 '19 11:06

lhk


People also ask

How do I force a widget to rebuild in Flutter?

Using setState to rebuild widgets Flutter gives you access to setState() . In this case, we have to ensure setState() has the new values. When setState() is called, Flutter will know to get these new values and mark the widget that needs to be rebuilt.

How do I stop widgets from rebuilding?

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.

How do you stop rebuild in Flutter?

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. Thanks to that const keyword, the instance of DecoratedBox will stay the same even if build were called hundreds of times.

What is widget lifecycle in Flutter?

Widget Lifecycle Methods:The life cycle is based on the state and how it changes. A stateful widget has a state so we can explain the life cycle of flutter based on it. Stage of the life cycle: createState. initState()


1 Answers

The most common reasons for a widget to rebuild are:

  • its parent rebuilt (whatever the reason is)
  • Element.markNeedsBuild have been called manually (typically using setState)
  • an inherited widget it depends on updated

Const instance of widgets are immune to the first reason, but they are still affected by the two others.

This means that a const instance of a StatelessWidget will rebuild only if one of the inherited widget it uses update.

like image 66
Rémi Rousselet Avatar answered Oct 18 '22 21:10

Rémi Rousselet