Widget configuration, a State object’s lifetime, and an application’s foreground/background status are different layers. This fourth chapter brings them together without treating them as one lifecycle.
Choose where the state lives
A stateless widget describes UI from its inputs. A stateful widget has a separate State object that can survive configuration updates while the framework keeps the corresponding element.
Putting all mutable values in one parent makes ownership simple initially, but broad updates can involve more of the tree. Separating state into child widgets narrows some updates while adding coordination work. Neither arrangement guarantees a performance result merely from the number of classes or states.
Initialization and post-frame work
The source explicitly initialized the binding before its pre-launch work:
WidgetsFlutterBinding.ensureInitialized();
It also scheduled work after a frame:
class AppContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => onAfterBuild(context));
return …..;
}
// @nhancv 10/25/2019: After widget initialized.
void onAfterBuild(BuildContext context) {
// do anything here
}
}
Scheduling in build can register again on later builds. A post-frame callback is a timing tool, not a guarantee that an operation should run exactly once.
Observe the application lifecycle
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
@override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
// Background
}
if (state == AppLifecycleState.resumed) {
// Foreground
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppLifecycleState'),
),
body: Center(),
);
}
}
The observer’s registration and removal belong to its owning state’s lifetime. The states and platform behavior in an older snippet need comparison with the current AppLifecycleState documentation.
Compare two update boundaries
The original counter updated through setState:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print('build $_counter');
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
EmptyWidget(),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class EmptyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('build empty widget');
return Container();
}
}
Its alternative used bflutter streams around the changing value:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final bloc = BlocDefault<int>();
void _incrementCounter() {
_counter++;
bloc.push(_counter);
}
@override
Widget build(BuildContext context) {
print('build $_counter');
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
EmptyWidget(),
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: bloc.stream,
initialData: 0,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class EmptyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('build empty widget');
return Container();
}
}
The narrower listener boundary changed what the example’s logs showed. That observation is not proof that streams remove all rebuilds or always outperform setState. Review subscriptions, error paths, state ownership, and disposal together. These historical examples were not rebuilt or measured for this revision.