showDialog() and push() cause a crash if pop() was called on the previous window before the opening animation was completed
Describe the bug showDialog() and push() cause a crash if pop() was called on the previous window before the opening animation was completed
Environment
name: untitled2
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.4.3 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
flutter_modular: ^6.3.4
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
To Reproduce You need to do a pushNamed to a screen, then do a pop() on that screen until the animation is complete, then immediately call showDialog() or push(). This results in the following error:
======== Exception caught by widgets library =======================================================
The following StateError was thrown building Builder:
Bad state: Future already completed
The relevant error-causing widget was:
MaterialApp MaterialApp:file:///Users/gleb.klimov/StudioProjects/untitled2/lib/main.dart:25:26
When the exception was thrown, this was the stack:
#1 Route.didComplete (package:flutter/src/widgets/navigator.dart:425:19)
#2 _RouteEntry.handleComplete (package:flutter/src/widgets/navigator.dart:3083:11)
#3 NavigatorState._flushHistoryUpdates (package:flutter/src/widgets/navigator.dart:4294:17)
#4 NavigatorState._updatePages (package:flutter/src/widgets/navigator.dart:4198:5)
This is because Navigator:didComplete is called 3 times after these actions, and the third time calling _popCompleter.complete() results in an error because it was already called the first time:
I/flutter ( 8768): Navigator:didComplete, _popCompleter is called with null, settings: /second, isCompleted: false
I/flutter ( 8768): Navigator:didComplete, _popCompleter is called with null, settings: null, isCompleted: false
I/flutter ( 8768): Navigator:didComplete, _popCompleter is called with null, settings: /second, isCompleted: true
Code for reproducing:
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
void main() => runApp(const MyApp());
class AppModule extends Module {
static const route = "/";
@override
void routes(RouteManager r) {
r.redirect('/', to: '/first');
r.child('/first', child: (context) => const FirstPage());
r.child('/second', child: (context) => const SecondPage());
r.child('/third', child: (context) => const ThirdPage());
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ModularApp(
module: AppModule(),
child: MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
routerConfig: Modular.routerConfig,
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('First page')),
body: Column(
children: [
const Center(child: Text('This is the first page')),
OutlinedButton(onPressed: () => _testCase1(context), child: const Text('Navigate to the second page and show a dialog')),
OutlinedButton(onPressed: () => _testCase3(context), child: const Text('Navigate to the second page and then to the third using push()')),
],
),
);
}
void _testCase1(BuildContext context) {
Modular.to.pushNamed('/second').then((value) {
showDialog(context: context, builder: (context) => _getAlertDialog());
});
}
void _testCase3(BuildContext context) {
Modular.to.pushNamed('/second').then((value) {
Modular.to.push(MaterialPageRoute(builder: (context) => const ThirdPage()));
});
}
Widget _getAlertDialog() {
return AlertDialog(
title: const Text('You most likely won\'t see this dialog.'),
actions: [
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('Close'),
)
],
);
}
}
class SecondPage extends StatefulWidget {
const SecondPage({super.key});
@override
State<SecondPage> createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
@override
void initState() {
// do pop() before finishing the animation
Future.delayed(const Duration(milliseconds: 80)).then((value) => Modular.to.pop());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text('Second page')));
}
}
class ThirdPage extends StatelessWidget {
const ThirdPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Third page')),
body: const Center(
child: Text('This is the third page'),
),
);
}
}
Screenshots
Good afternoon!
I'd like to check with Contributors: does anyone understand what the problem is and how difficult it is to fix? As a result of this bug, we have no way to upgrade to the new version of modular (the old version has a workaround). Maybe someone could suggest a rough plan to fix the problem so that I can countertribute with less time-consuming code study?
Hi @jacobaraujo7 @edugemini @steniooliv , do you have any insights on this issue? Any information would be greatly appreciated. Thanks!
Have you tried closing the AlertDialog with Navigator.pop(context) using the context of showDialog?
Personally I prefer to avoid using Modular.pop to close Widgets opened with showDialog, showModalBottomsheet, Navigator.push....
I use Modular.pop only to close screens opened with Modular.to.push and .pushNamed
I agree here as well. I've had issues with Modular pop() but Navigator pop() always works fine
feito. Subirar na proxima atualizaçÃo