modular icon indicating copy to clipboard operation
modular copied to clipboard

showDialog() and push() cause a crash if pop() was called on the previous window before the opening animation was completed

Open KlGleb opened this issue 1 year ago • 2 comments

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

KlGleb avatar Dec 19 '24 01:12 KlGleb

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?

KlGleb avatar Jan 20 '25 10:01 KlGleb

Hi @jacobaraujo7 @edugemini @steniooliv , do you have any insights on this issue? Any information would be greatly appreciated. Thanks!

KlGleb avatar Feb 27 '25 10:02 KlGleb

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

OrlandoEduardo101 avatar Mar 11 '25 18:03 OrlandoEduardo101

I agree here as well. I've had issues with Modular pop() but Navigator pop() always works fine

vagrantrobbie avatar Mar 18 '25 05:03 vagrantrobbie

feito. Subirar na proxima atualizaçÃo

jacobaraujo7 avatar Jun 13 '25 15:06 jacobaraujo7