Irbisa · Cheatsheet
Flutter Internals
Three trees, RenderObject, Element tree, BuildOwner, rendering pipeline
What are Flutter's bindings (
WidgetsFlutterBinding,SchedulerBinding,RendererBinding,GestureBinding), and what does each one own?Explain Flutter's three trees: Widget, Element, RenderObject. Why three trees instead of one?
How does Flutter draw its UI without native OEM widgets, and what do the framework, engine and embedder each do?
Toggling the switch below resets
Counterto zero every time. Why does it lose itsState, and how do you keep it?@overrideWidget build(BuildContext context) { const counter = Counter(); // StatefulWidget that keeps its own count return Column( children: [ Switch( value: _padded, onChanged: (v) => setState(() => _padded = v), ), _padded ? const Padding(padding: EdgeInsets.all(16), child: counter) : counter, ], );}What is an Element's lifecycle (mount, update, deactivate, activate, unmount), and when does
State.disposeactually run?How does Flutter's rendering pipeline work, and what happens between a
setStateand pixels on screen?void main() { WidgetsFlutterBinding.ensureInitialized(); // Real frame timings from the engine, also in release builds SchedulerBinding.instance.addTimingsCallback((timings) { for (final t in timings) { if (t.totalSpan > const Duration(milliseconds: 16)) {…What is the difference between transient, persistent and post-frame callbacks, and when do you use
addPostFrameCallback?class _HeaderState extends State<Header> { final _key = GlobalKey(); double? _height; @override void initState() {…Wrapping a
Columnchild inPositionedthrows "Incorrect use of ParentDataWidget". What wasPositionedactually trying to do?class TrayParentData extends ContainerBoxParentData<RenderBox> { double weight = 1;} class Weighted extends ParentDataWidget<TrayParentData> { const Weighted({super.key, required this.weight, required super.child});…This setter runs on every rebuild through
updateRenderObject, and the timeline shows layout every frame. What is wrong with it?class RenderBadge extends RenderBox { RenderBadge({required Color color}) : _color = color; Color _color; set color(Color value) { _color = value; markNeedsLayout(); } @override void performLayout() => size = constraints.constrain(const Size.square(24)); @override void paint(PaintingContext context, Offset offset) => context.canvas .drawCircle(offset + size.center(Offset.zero), 12, Paint()..color = _color);}Find the bug:
StatusDotkeeps its first color even after its parent rebuilds it with a new one.class StatusDot extends LeafRenderObjectWidget { const StatusDot({super.key, required this.color}); final Color color; @override RenderStatusDot createRenderObject(BuildContext context) => RenderStatusDot(color);}class RenderStatusDot extends RenderBox { RenderStatusDot(this._color); Color _color; set color(Color v) { if (v == _color) return; _color = v; markNeedsPaint(); } @override void performLayout() => size = constraints.constrain(const Size.square(12)); @override void paint(PaintingContext context, Offset offset) => context.canvas.drawCircle(offset + size.center(Offset.zero), 6, Paint()..color = _color);}How does hit testing work in Flutter, and why is a child that overflows its parent not tappable?
// A round button: hits in the square corners fall through to whatever is behindclass RenderRoundButton extends RenderProxyBox { @override bool hitTest(BoxHitTestResult result, {required Offset position}) { final center = size.center(Offset.zero); if ((position - center).distance > size.shortestSide / 2) return false;…You push an opaque route over a page whose
AnimationControlleris running. Does that controller keep ticking underneath?@overridevoid initState() { super.initState(); // wrong: if an opaque route covers this page first, the future waits // until the user comes back, because the ticker is muted, not finished // _controller.forward().then((_) => _startTimer());…What is
debugFillPropertiesfor, and how do you make a custom widget show its fields in the Flutter Inspector?class PriceTag extends StatelessWidget { const PriceTag({super.key, required this.amount, this.currency = 'USD', this.onSale = false}); final double amount; final String currency; final bool onSale;…Why can Flutter afford to rebuild thousands of widgets per frame, and when does that stop being true?
AnimatedBuilder( animation: _controller, // The same child instance comes back on every tick, so updateChild // sees identical(old, new) and skips the whole chart child: const ExpensiveChart(), builder: (context, child) => Transform.rotate(…A parent and one of its descendants both call
setStatebefore the same frame. How many times does the descendant build?A route transition throws "Duplicate GlobalKeys detected in widget tree". What is a
GlobalKeyactually doing under the hood?class _GalleryState extends State<Gallery> { // Created once, owned by one State final _playerKey = GlobalKey<VideoPlayerState>(); bool _fullscreen = false; @override…What are
BuildOwnerandPipelineOwner, and why is the work split between them?A render object calls
markNeedsLayout. How far up the tree does that dirt travel, and what stops it?class RenderStripe extends RenderBox with RenderObjectWithChildMixin<RenderBox> { RenderStripe(this._thickness); double _thickness; set thickness(double v) { if (v == _thickness) return;…Find the bug: after the text inside
RenderCaptionchanges, the padded caption keeps its old size or the debug build asserts.class RenderCaption extends RenderShiftedBox { RenderCaption() : super(null); static const double padding = 8; @override void performLayout() { final child = this.child!; child.layout(constraints.deflate(const EdgeInsets.all(padding))); (child.parentData! as BoxParentData).offset = const Offset(padding, padding); size = constraints.constrain( Size(child.size.width + padding * 2, child.size.height + padding * 2), ); }}How does compositing work in Flutter, and when does a render object get its own layer?
Why can't
LayoutBuilder's builder callsetStateon an ancestor, and how does it build widgets in the middle of layout at all?// wrong: the ancestor's build is over, layout is running right nowLayoutBuilder(builder: (context, constraints) { setState(() => _width = constraints.maxWidth); // asserts return Content(width: _width);});…How do you write a custom
RenderBox, and when is it the right tool instead of composing widgets or aCustomPainter?class Inset extends SingleChildRenderObjectWidget { const Inset({super.key, required this.gap, super.child}); final double gap; @override RenderObject createRenderObject(BuildContext context) => RenderInset(gap); @override…