I have a Flutter Web project. I have two pages mainly: one where I list sports, and another where I modify a particular sport. To navigate I use the context.router.navigateNamed method of the auto_route package.
When I first navigate to my edit page, everything works correctly, but if I navigate to the edit page a second time (by navigating to the list page and pressing another sport to edit), the page is reused because it was already in the stack (as expected), but the Futurebuilder does not work correctly (it does not show the loading, show the old snapshot.data, and directly jumps to showing the new snapshot.data).
This is my code:
class EditSportView extends StatefulWidget {
final int id;
const EditSportView({Key? key, @PathParam('id') required this.id})
: super(key: key);
@override
_EditSportViewState createState() => _EditSportViewState();
}
class _EditSportViewState extends State<EditSportView> {
late SportProvider sportProvider;
late MenuProvider menuProvider;
final GlobalKey<FormState> _keyForm = GlobalKey<FormState>();
late TextEditingController _nameController;
final List<GameModeEntity> selectedGameModes = [];
Timer? _timer;
@override
initState() {
super.initState();
sportProvider = Provider.of<SportProvider>(context, listen: false);
menuProvider = Provider.of<MenuProvider>(context, listen: false);
_nameController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0),
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: CustomCard(
boxShadowBlurRadius: 0.0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
child: FutureBuilder<List>( // <-------- The problem seems to be here
future: Future.wait([
sportProvider.getSport(GetSportRequest(widget.id)),
sportProvider.getAllGameModes(),
]),
builder: (context, snapshot) {
if (snapshot.hasData &&
!snapshot.data!.any((element) => element == null)) {
SportEntity sport = snapshot.data![0];
_nameController.text = sport.name;
List<GameModeEntity> gameModes = snapshot.data![1];
return Form(
key: _keyForm,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
const CustomTextTitle(
width: double.infinity, text: "Sport Settings"),
const CustomDivider(),
_nameTextFormField(),
_gameModeSelectionFormField(
gameModes,
gameModes
.map((e) => sport.gameModes!
.any((element) => element.id == e.id))
.toList()),
_createEventButton(context),
_cancelEventButton(),
],
),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
),
),
),
);
}
Widget _nameTextFormField() {
return CustomTextFormField(
showCounter: true,
controller: _nameController,
isRequired: true,
showPrefixIcon: false,
showSuffixIcon: false,
autofocus: true,
topLabel: AppGlobals.nameText,
textColor: Colors.black,
validator: (String? value) {
if (value == null || value.isEmpty) {
return AppGlobals.fieldRequiredText;
}
if (!AppGlobals.onlyLettersNoSpaceRegExp.hasMatch(value)) {
return AppGlobals.invalidNameText;
}
return null;
},
onChanged: (String value) {},
);
}
Widget _gameModeSelectionFormField(
List<GameModeEntity> gms, List<bool> initialSelection) {
return CustomGenericFormField<GameModeEntity>(
topLabel: "Game Modes",
items: gms.map((gm) => MultiSelectItem(gm, gm.label)).toList(),
initialValue: gms
.asMap()
.entries
.where((e) => initialSelection[e.key])
.map((e) => e.value)
.toList(),
validator: (values) {
if (values?.isEmpty ?? true) {
return "Select at lease one";
}
return null;
},
onTap: (values) {
selectedGameModes.clear();
for (var element in values.where((e) => e != null)) {
selectedGameModes.add(element!);
}
},
);
}
Container _createEventButton(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: SizedBox(
width: 180,
child: CustomOutlinedButton(
padding: const EdgeInsets.symmetric(vertical: 5.0),
text: "Update Sport",
borderColor: AppGlobals.selectedSeparatorColor,
isFilled: true,
backgroundColor: AppGlobals.primaryColor,
borderRadius: 5.0,
textSize: 14,
onPressed: () async {
if (_keyForm.currentState!.validate()) {
context.loaderOverlay.show();
UpdateSportRequest request = UpdateSportRequest(
id: widget.id,
name: _nameController.text,
gameModes: selectedGameModes.map((e) => e.id).toList());
sportProvider.updateSport(request).then((value) {
context.loaderOverlay.hide();
if (value ?? false) {
showDialog(
context: context,
barrierDismissible: false,
builder: (buildContext) {
_timer = Timer(const Duration(seconds: 2), () {
Navigator.of(buildContext).pop();
menuProvider
.setCurrentPageUrl(RouteGlobals.listSportsRoute);
context.router
.navigateNamed(RouteGlobals.listSportsRoute);
});
return const ConfirmationDialog(
msg: AppGlobals.sportUpdateSuccessfullyText);
},
);
}
});
}
},
),
),
);
}
Container _cancelEventButton() {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: SizedBox(
width: 180,
child: CustomOutlinedButton(
padding: const EdgeInsets.symmetric(vertical: 5.0),
text: AppGlobals.cancelText,
borderColor: AppGlobals.selectedSeparatorColor,
isFilled: true,
backgroundColor: Colors.black87,
borderRadius: 5.0,
textSize: 14,
onPressed: () {
menuProvider.setCurrentPageUrl(RouteGlobals.listSportsRoute);
context.router.navigateNamed(RouteGlobals.listSportsRoute);
},
),
),
);
}
}
Can someone explain to me why this happens? Thank you.
UPDATE Following the comments I changed my code as follows:
(...)
@override
initState() {
super.initState();
sportProvider = Provider.of<SportProvider>(context, listen: false);
menuProvider = Provider.of<MenuProvider>(context, listen: false);
_nameController = TextEditingController();
future = Future.wait([
sportProvider.getSport(GetSportRequest(widget.id)),
sportProvider.getAllGameModes(),
]);
}
@override
void didUpdateWidget(covariant EditSportView oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.id != widget.id) {
future = Future.wait([
sportProvider.getSport(GetSportRequest(widget.id)),
sportProvider.getAllGameModes(),
]);
}
}
(...)
FutureBuilder<List>(
future: future,
builder: (...)
But unfortunately I continue to have the same problems. When the widget is updated for the second time, it is as if the Futurebuilder does not update until the Future call finishes again, even when the build method has been invoked.

You should provide future (variable) instead of future function.
Try this
Detailed explanation https://stackoverflow.com/a/77760361/3552066