I'm new to flutter and trying to use two Stateful Widgets first one calling the second in build()
method and I just want to update the child widget variable that is passed from parent in the constructor.
Here is the code I'm trying with.
Parent Widget
class Parent extends StatefulWidget {
Parent({Key? key}) : super(key: key);
@override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
List appointments = [];
@override
void initState() {
super.initState();
fetchAppointments();
}
@override
Widget build(BuildContext context) {
return Builder(builder: (BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
_pullRefresh();
},
child: ListView(
children: [
AppointmentsWidget(appointments: appointments) // <----- I passed the variable in constructor and this one is updating in setState and I want it to update in the child widget too
],
),
),
);
});
}
_pullRefresh() {
fetchAppointments();
}
fetchAppointments() {
setState(() {
// Stuff to do
appointments = ......
......
......
});
}
}
Child Widget
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
@override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(appointments: appointments); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
var appointments;
_AppointmentsWidgetState({this.appointments}); // <--- Constructor 2
@override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
I know the constructor calls once but I couldn't find a way to either recall the constructor OR somehow pass the updated value to the constructor.
Your help is really appreciated.
CodePudding user response:
You should make your child widget stateless, as its state (the appointments) are handled by the parent. What happens currently is that your child widget is constructed, where the empty list is used as its widget.appointments
value. Then when the appointments have been fetched, the widget.appointments
rebuilds, but since the state of the child is maintained, this value is not passed on (initState
of the child is not re-run).
class AppointmentsWidget extends StatelessWidget {
final List appointments;
const AppointmentsWidget({Key? key, required this.appointments}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
Also, take a look at the flutter docs on handling state: https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro
These also state that it's good to keep your state high up (in the parent in this case), and make child widgets use the state of the parents to render themselves appropriately.
Rectification
As you mention in the comments, you need the child widget to be stateful (for maintaining state on some other data). In that case, you can simply get rid of the appointments
state variable and use widget.appointments
instead, which will update when the parent rebuilds with a new value.
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
@override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
_AppointmentsWidgetState(); // <--- Constructor 2
@override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(widget.appointments[index].toString()); // <--- This is where I use it
},
);
}
}
CodePudding user response:
You need some listenable to update the widget because the context
of parent and child are diferents. In that case the correct way to do it (without a state managment package) is with a InheritedWidget
.
Inherited class:
class ExampleInherited extends InheritedWidget {
final Widget child;
final ExampleBloc exampleBloc; // <-- change notifier that can do changes and notify their children
const ExampleInherited({Key? key, required this.child, required this.exampleBloc}) : super(key: key, child: child);
static ExampleInherited? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ExampleInherited>();
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => true;
}
ChangeNotifier class:
class ExampleBloc extends ChangeNotifier {
static final ExampleBloc _exampleBloc = ExampleBloc._internal();
factory ExampleBloc() {
return _exampleBloc;
}
ExampleBloc._internal();
exampleMethod(){
// here you can do whatever you need (update vars)
notifyListeners(); // <-- this notifies the children that they need to be rebuilded
}
}
Then set this in your parent view:
ExampleInherited(
exampleBloc: ExampleBloc(),
child: // content
}
And then in your child view:
@override
Widget build(BuildContext context) {
ExampleBloc exampleBloc = ExampleInherited.of(context)!.exampleBloc;
return AnimatedBuilder(
animation: exampleBloc,
builder: (context, child) {
return //your content
// this will rebuild every time you call notifyListeners() in your bloc
})
}