Logging lifecycle methods is a useful way to observe which state owns a rebuild. This older experiment nested stateful and stateless widgets, then placed buttons at different levels.
The widget tree
/// App
/// WgFullState
/// + Button
/// + Container
/// + Button
/// + SubWgStateless
/// + Container
/// + SubWgFullState
/// + Button
/// + SubWgStateless
/// WgStateless
/// + WgFullState
/// + Button
/// + Container
/// + Button
/// + SubWgStateless
/// + Container
/// + SubWgFullState
/// + Button
/// + SubWgStateless
The recorded startup
Restarted application in 1,955ms.
I/flutter (19233): app build
I/flutter (19233): initState WgFullState
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): initState SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): build WgStateless
I/flutter (19233): initState WgFullState
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): initState SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): app build
I/flutter (19233): didUpdateWidget WgFullState
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): didUpdateWidget SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): build WgStateless
I/flutter (19233): didUpdateWidget WgFullState
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): didUpdateWidget SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
The log shows more than one build pass in that run. It does not establish that every Flutter application always builds twice at startup or that initState itself is a general cause of a second application build.
Compare button ownership
The two parent-level buttons produced these recorded sequences:
I/flutter (19233): setState WgFullState
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): didUpdateWidget SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): setState WgFullState Btn2
I/flutter (19233): build WgFullState
I/flutter (19233): build SubWgStateless
I/flutter (19233): didUpdateWidget SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
The nested stateful widget’s button produced a narrower sequence:
I/flutter (19233): setState SubWgFullState
I/flutter (19233): build SubWgFullState
I/flutter (19233): build SubWgStateless
The next three source logs repeated these patterns and are omitted. setState marks the owning state for a rebuild; how descendants update depends on the resulting widget tree and framework reconciliation, not an unconditional rule that every descendant must do identical work.
The preserved experiment
Read the historical widget implementation
import 'package:flutter/material.dart';
class RDApp extends StatefulWidget {
@override
_RDAppState createState() => _RDAppState();
}
/// WgFullState
/// + Wg1Statless
/// + Wg1FullState
/// WgStatless
class _RDAppState extends State<RDApp> {
@override
Widget build(BuildContext context) {
print('app build');
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: Column(
children: <Widget>[
WgFullState(),
WgStateless(),
],
),
),
);
}
}
class WgFullState extends StatefulWidget {
@override
_WgFullState1State createState() => _WgFullState1State();
}
class _WgFullState1State extends State<WgFullState> {
@override
void initState() {
print('initState WgFullState');
super.initState();
}
@override
void didUpdateWidget(WgFullState oldWidget) {
print('didUpdateWidget WgFullState');
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print('build WgFullState');
return Container(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('WgFullState'),
onPressed: () {
setState(() {
print('setState WgFullState');
});
},
),
Container(
child: RaisedButton(
child: Text('WgFullState Btn2'),
onPressed: () {
setState(() {
print('setState WgFullState Btn2');
});
},
),
),
SubWgStateless(),
SubWgFullState(),
],
)
);
}
}
class SubWgFullState extends StatefulWidget {
@override
_SubWgFullStateState createState() => _SubWgFullStateState();
}
class _SubWgFullStateState extends State<SubWgFullState> {
@override
void initState() {
print('initState SubWgFullState');
super.initState();
}
@override
void didUpdateWidget(SubWgFullState oldWidget) {
print('didUpdateWidget SubWgFullState');
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print('build SubWgFullState');
return Container(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('SubWgFullState'),
onPressed: () {
setState(() {
print('setState SubWgFullState');
});
},
),
SubWgStateless()
],
)
);
}
}
class WgStateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('build WgStateless');
return Container(
child: WgFullState(),
);
}
}
class SubWgStateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('build SubWgStateless');
return Container();
}
}
Source: original Gist revision. The old widget APIs and recorded timings were not rerun for this revision.