I cannot find a satisfactory way for a grandchild widget to trigger a grandparent state change. My app saves and sources its data all from an on-device database. Ive tried to proceed this far without using a state management library as I thought this was overkill - the app is not complex.
Ive got a ListView
(grandparent), which in turn has children that are my own version of ListTiles
. There are two icon buttons on each ListTile
, one to edit and one to delete - both of which trigger a different alertdialog
(grandchild) popup. When I perform an update or delete on the data, it is written to the db and a Future
is returned - and then I need the grandparent ListView
state to refresh. StatefulBuilders
will only give me a way to refresh state on the grandchild (separately from the child), not a way to trigger 'multi level' state change.
Is it time for a state management solution such as BLOC or Riverpod, or is there any other solution?
ListView
Grandparent Widget
@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// other children here
Expanded(
flex: 11,
child: FutureBuilder<List<MyCustomObject>>(
future: _getQuotes(), // queries the db
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting
&& !snapshot.hasData) {
return const Center(
child: SizedBox(
height: AppDims.smallSizedBoxLoadingProgress,
width: AppDims.smallSizedBoxLoadingProgress,
child: CircularProgressIndicator()
),
);
} else if (snapshot.hasError) {
log(snapshot.error.toString());
log(snapshot.stackTrace.toString());
return Center(child: Text(snapshot.error.toString()));
} else {
// no point using StatefulBuilder here, as i need
// to potentially trigger _getQuotes() again to rebuild the entire ListView
return ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: AppDims.textHorizontalPadding,
vertical: AppDims.textVerticalPadding
),
itemCount: snapshot.data!.length,
itemBuilder: (context, int index) {
return MyCustomTile(
// tile data mapping from snapshot for MyCustomObject
);
},
);
}
},
)
)
]
);
}
)
);
}
MyCustomTile
Child Widget
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDims.tileBorderRadius),
side: const BorderSide(
color: Colors.green,
width: 1.5,
)
),
child: ListTile(
// other omitted ListTile params here
trailing: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return EditDialog();
}
).then((_) => setState(() {})), // will only setState on the dialog!
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => DeleteWarningDialog(
widget.id,
AppStrings.price.toLowerCase(),
true
),
),
),
]
),
),
);
}
DeleteWarningDialog
Grandchild Widget
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(_buildFinalWarningString()),
actions: [
TextButton(
child: const Text(AppStrings.cancel),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: const Text(AppStrings.delete),
onPressed: () {
_appDatabase.deleteFoo(widget.objectIdToDelete);
Navigator.pop(context);
},
)
],
);
}
CodePudding user response:
you can create a function that updates the state
of the grandparent widget and pass it from grandparent to parent then from parent to grandchild and use it wherever you want to update the grandparent widget
CodePudding user response:
you will have to declare a function in the grandParent which is the listView in your case and pass it to parent and children's. but it will be so complicated and not really efficient, using state management would make it a lot easer and clean