Skip to content

Instantly share code, notes, and snippets.

@ultraon
Created December 2, 2024 21:20
Show Gist options
  • Save ultraon/3d39d40f08d79c3c4e9316f127e5d146 to your computer and use it in GitHub Desktop.
Save ultraon/3d39d40f08d79c3c4e9316f127e5d146 to your computer and use it in GitHub Desktop.
The sample shows that the const widget can be skipped by Flutter for rebuilding.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DemoPage extends StatelessWidget {
const DemoPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => DependentCounterCubit(),
child: const Scaffold(
body: DemoView(),
),
);
}
}
class DemoView extends StatelessWidget {
const DemoView({super.key});
@override
Widget build(BuildContext context) {
print('DemoView build, $hashCode');
final state = context.watch<DependentCounterCubit>().state;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const CounterText1(),
CounterText2('${state.counterTwo}'),
const SizedBox.square(dimension: 20),
FilledButton(
onPressed: context.read<DependentCounterCubit>().incrementOne,
child: const Text('Increment One'),
),
FilledButton(
onPressed: context.read<DependentCounterCubit>().incrementTwo,
child: const Text('Increment Two'),
),
],
),
);
}
}
// This class is only created for tagging it in the Widget Performance view
class CounterText1 extends StatelessWidget {
const CounterText1({super.key});
@override
Widget build(BuildContext context) {
print('CounterText1 build, $hashCode');
final counterText = context.select(
(DependentCounterCubit c) => c.state.counterOne,
);
return Text(
'Text1: $counterText',
style: Theme.of(context).textTheme.titleLarge,
);
}
}
// This class is only created for tagging it in the Widget Performance view
class CounterText2 extends StatelessWidget {
const CounterText2(this._text, {super.key});
final String _text;
@override
Widget build(BuildContext context) {
print('CounterText2 build, $hashCode');
return Text(
'Text2: $_text',
style: Theme.of(context).textTheme.titleLarge,
);
}
}
// CUBIT SAMPLE
typedef DependentCounterState = ({int counterOne, int counterTwo});
class DependentCounterCubit extends Cubit<DependentCounterState> {
DependentCounterCubit() : super((counterOne: 0, counterTwo: 0));
void incrementOne() {
emit((counterOne: state.counterOne + 1, counterTwo: state.counterTwo));
}
void incrementTwo() {
emit((counterOne: state.counterOne, counterTwo: state.counterTwo + 1));
}
}
@ultraon
Copy link
Author

ultraon commented Dec 2, 2024

The const keyword significantly impacts widget performance, especially noticeable during numerous rebuilds across many widgets.
Here are some clarifications:

  1. Performance Gains:
    Using const can lead to performance improvements because it allows Flutter to reuse instances of immutable widgets across multiple rebuilds. This is particularly beneficial in complex UIs with many static elements.
  2. Build and Instantiation:
    There is a common confusion between “building” and “instantiating” in the official documentation that could be clarified. The term “building” often refers to invoking the build method, which describes the widget tree. In contrast, “instantiating” refers to creating an instance of a widget. Although a const widget can be rebuilt multiple times, its instance remains unchanged, thus saving on allocation and reducing garbage collection overhead.
  3. Behavior in All Build Modes:
    It’s crucial to understand that const is effective in all build modes (debug, profile, and release). The Dart VM optimizes performance by reusing const objects wherever possible, not just in release mode.
  4. Selective Rebuilding:
  • Flutter optimizes the rebuilding process by not calling the build method on const widgets if their descendant widgets are not marked as dirty. For instance, if a parent widget is listening to a state change and contains a Column with a const widget as a child, and neither the child nor any descendants depend on the changed state, then the const widget will not be rebuilt. This selective rebuilding helps in further enhancing performance by avoiding unnecessary widget tree rebuilds.
  • see the comment https://gist.github.com/ultraon/3d39d40f08d79c3c4e9316f127e5d146?permalink_comment_id=5314599#gistcomment-5314599
  1. Understanding const Instances:
  • A const widget creates a single, canonical instance when instantiated with the same parameters. This can be visualized as a cache where each unique set of parameters stores one instance of the widget.
  • You can have multiple const instances of the same widget class, each differentiated by its parameters.
class WidgetConstA extends StatelessWidget {
  final String text;
  const WidgetConstA({required this.text});
  // `operator ==` isn't overridden here, so comparison defaults to identity
}

class WidgetNonConstB extends StatelessWidget {
  final String text;
  WidgetNonConstB({required this.text});
  // `operator ==` isn't overridden here either
}

const widgetA1 = WidgetConstA(text: 'A');
const widgetA2 = WidgetConstA(text: 'A');
final isIdenticalA = identical(widgetA1, widgetA2);  // prints `true`, same instance
final isEqualA = widgetA1 == widgetA2;               // prints `true`, identity implies equality

final widgetB1 = WidgetNonConstB(text: 'B');
final widgetB2 = WidgetNonConstB(text: 'B');
final isIdenticalB = identical(widgetB1, widgetB2);  // prints `false`, different instances
final isEqualB = widgetB1 == widgetB2;               // prints `false`, different instances

This distinction underscores the importance of const in managing memory efficiency and performance in Flutter applications. Both StatelessWidget and StatefulWidget can benefit from const constructors, with mutable state managed within the State.

Summary:

  • the const avoids re-instantiation of the const widget with the same const params, the instance is resolved during compilation time and it doesn't need to be collected by GC
  • Flutter Framework can skip calling the build method of the const widget during the re-building phase in case the child widgets are not marked as dirty, so, they don't need to be rebuilt

@ultraon
Copy link
Author

ultraon commented Dec 2, 2024

!!! PAY ATTENTION !!!

When I click on the "Increment One" button it shows:
Click 1:

DemoView build, 301182698
CounterText1 build, 575811888 // <------------- the instance is the same, but the `build` method is called.
CounterText2 build, 794060487

Click 2:

DemoView build, 301182698
CounterText1 build, 575811888 // <------------- the instance is the same, but the `build` method is called.
CounterText2 build, 569557191

When I click on the "Increment Two" button it shows:
Click 1:

DemoView build, 301182698
// ----------- CounterText1 isn't rebuilt
CounterText2 build, 210843644

Click 2:

DemoView build, 301182698
// ----------- CounterText1 isn't rebuilt
CounterText2 build, 288415978

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment