I am trying to understand how state and lifting state works in Flutter. In the code below I made a simple app where I tried to pass or lift a string and a function through multiple levels.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String data = 'MyApp class data';
void setTextField(String s) {
setState(
() {
data = s;
},
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainApp(data, setTextField),
);
}
}
class MainApp extends StatelessWidget {
final String mainAppData;
final Function setTextField;
MainApp(this.mainAppData, this.setTextField);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(mainAppData)),
body: Container(
child: Level2(mainAppData, setTextField),
),
);
}
}
class Level2 extends StatelessWidget {
final String level2Data;
final Function setTextField;
Level2(this.level2Data, this.setTextField);
@override
Widget build(BuildContext context) {
return Container(
child: Level3(level2Data, setTextField),
);
}
}
class Level3 extends StatefulWidget {
final String level3Data;
final Function setTextField;
Level3(this.level3Data, this.setTextField);
@override
_Level3State createState() {
print('Level3: $this.level3Data');
return _Level3State(this.level3Data, this.setTextField);
}
}
class _Level3State extends State<Level3> {
String level3Data;
Function setTextField;
_Level3State(this.level3Data, this.setTextField);
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
onChanged: setTextField,
),
Text(level3Data),
],
);
}
}
When the app starts, the string data
in the _MyAppState
state class is passed to the MainApp
StatelessWidget when MainApp
is built. In a similar manner, info is passed then to Level2
StatelessWidget and Level3
StatefulWidget.
When the TextField
in Level3
changes, setTextField
is triggered. setTextField
is defined up in MyApp
and I figured that when the String data
changes (inside setState()
) it should trigger rebuilding of all the widgets affected by the change.
The text in the MainApp
widget (appBar) changes, showing MainApp
was rebuilt as I expected. But nothing happens to the Level3
widget; it's not rebuilt. The print statement in the createState()
method does not execute, and the Text widget's text does not change.
Why doesn't the Level3
StatefulWidget rebuild?
In my understanding, the stateful widget Level3 is already created at the given location in the tree. And according to the contract of the stateful widget, It is the responsibility of the widget implementer to ensure that the State is promptly notified when such state changes, using State.setState. So in your case you need to have setstate in level3 to be notified of the change again. The rebuilding only happens at level 1 because setState is called .
The rebuilding process continues recursively until the description of the user interface is fully concrete. In Level 3, the class has its own set of fields String level3Data; Function setTextField; It means these need to be again set using SetState or if you do something like this then the widget building continues.
class Level3 extends StatefulWidget {
final String level3Data;
final Function setTextField;
Level3(this.level3Data, this.setTextField);
@override
_Level3State createState() {
print('Level3: $this.level3Data');
return _Level3State();
}
}
class _Level3State extends State<Level3> {
// String level3Data;
// Function setTextField;
@override
Widget build(BuildContext _) {
return Column(
children: <Widget>[
TextField(
onChanged: widget.setTextField,
),
Text(widget.level3Data),
],
);
}
}