Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open modal before another is closed #152

Open
kmorl opened this issue May 31, 2024 · 4 comments
Open

Open modal before another is closed #152

kmorl opened this issue May 31, 2024 · 4 comments
Assignees
Labels
bug Something isn't working P1
Milestone

Comments

@kmorl
Copy link

kmorl commented May 31, 2024

We have a problem that when a modal is currently closing and the user opens another one at the same time, an error is thrown and the app no longer works properly. I added an example for this behavior.

Using the version: 0.7.0

doubleOpenModal.mov
`══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building NavigationSheetRouteContent(dirty, dependencies:
[_InheritedSheetExtentScope, _ModalScopeStatus]):
'package:smooth_sheets/src/navigation/navigation_sheet_extent.dart': Failed assertion: line 45 pos
12: '_localExtentScopeKeyRegistry.containsKey(route)': is not true.

The relevant error-causing widget was:
  NavigationSheetRouteContent
  NavigationSheetRouteContent:file:///Users/klaudia.morleo/.pub-cache/hosted/pub.dev/smooth_sheets-0.7.0/lib/src/navigation/navigation_routes.dart:73:12

When the exception was thrown, this was the stack:
#2      NavigationSheetExtent.getLocalExtentScopeKey (package:smooth_sheets/src/navigation/navigation_sheet_extent.dart:45:12)
#3      NavigationSheetRouteContent.build (package:smooth_sheets/src/navigation/navigation_route.dart:131:40)
#4      StatelessElement.build (package:flutter/src/widgets/framework.dart:5550:49)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5480:15)
#6      Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#7      StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#8      Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#9      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#10     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#11     StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#12     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#13     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#14     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#15     StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#16     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#17     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#18     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#19     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#20     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#21     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#22     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#23     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#24     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#25     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#26     Element.updateChildren (package:flutter/src/widgets/framework.dart:3973:32)
#27     MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6918:17)
#28     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#29     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#30     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#31     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#32     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#33     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#34     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#35     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#36     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#37     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#38     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#39     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#40     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#41     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#42     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#43     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#44     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#45     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#46     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#47     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#48     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#49     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#50     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#51     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#52     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#53     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#54     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#55     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#56     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#57     StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#58     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#59     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#60     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#61     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#62     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#63     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#64     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#65     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#66     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#67     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#68     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#69     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#70     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#71     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#72     ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#73     _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#74     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#75     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#76     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#77     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#78     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#79     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#80     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#81     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#82     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#83     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#84     ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#85     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#86     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#87     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#88     ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#89     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#90     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#91     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#92     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#93     StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#94     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#95     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#96     Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#97     StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#98     Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#99     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#100    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#101    StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#102    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#103    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#104    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#105    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#106    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#107    ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#108    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#109    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#110    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#111    ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#112    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#113    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#114    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#115    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#116    StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#117    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#118    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#119    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#120    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#121    StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#122    Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#123    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#124    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#125    Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#126    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2904:19)
#127    WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:989:21)
#128    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:448:5)
#129    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1386:15)
#130    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1311:9)
#131    SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1169:5)
#132    _invoke (dart:ui/hooks.dart:312:13)
#133    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:399:5)
#134    _drawFrame (dart:ui/hooks.dart:283:31)
(elided 2 frames from class _AssertionError)

════════════════════════════════════════════════════════════════════════════════════════════════════

Another exception was thrown: The sheet extent and the dimensions values must be finalized during the layout phase.
Another exception was thrown: Duplicate GlobalKey detected in widget tree.
Another exception was thrown: NavigationSheet: markAsDimensionsWillChange() was called more times than markAsDimensionsChanged() in a frame.`
@fujidaiti
Copy link
Owner

Hi @kmorl,

Can you post the code to reproduce this error?

@fujidaiti fujidaiti added need more information Lack of information to reproduce or determine if it is a bug or not bug Something isn't working P1 and removed need more information Lack of information to reproduce or determine if it is a bug or not labels Jun 1, 2024
@fujidaiti fujidaiti self-assigned this Jun 1, 2024
@fujidaiti fujidaiti added this to the v1 milestone Jun 1, 2024
@kmorl
Copy link
Author

kmorl commented Jun 3, 2024

It is basically a minimal version of the ai_playlist_generator from the cookbook.

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:smooth_sheets/smooth_sheets.dart';

void main() {
  // Make the system navigation bar transparent on Android.
  if (Platform.isAndroid) {
    WidgetsFlutterBinding.ensureInitialized();
    SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.edgeToEdge,
      overlays: [SystemUiOverlay.top],
    ).then((_) {
      SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith(
        systemNavigationBarColor: Colors.transparent,
        systemNavigationBarDividerColor: Colors.transparent,
      ));
    });
  }

  runApp(const _AiPlaylistGeneratorExample());
}

class _AiPlaylistGeneratorExample extends StatelessWidget {
  const _AiPlaylistGeneratorExample();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: router,
    );
  }
}

// ----------------------------------------------------------
// Routes
// ----------------------------------------------------------

final sheetTransitionObserver = NavigationSheetTransitionObserver();

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const _Root(),
      routes: [_sheetShellRoute],
    ),
  ],
);

// A ShellRoute is used to create a new Navigator for nested navigation in the sheet.
final _sheetShellRoute = ShellRoute(
  observers: [sheetTransitionObserver],
  pageBuilder: (context, state, navigator) {
    // Use ModalSheetPage to show a modal sheet.
    return ModalSheetPage(
      swipeDismissible: true,
      child: _SheetShell(
        navigator: navigator,
        transitionObserver: sheetTransitionObserver,
      ),
    );
  },
  routes: [_introRoute],
);

final _introRoute = GoRoute(
  path: 'intro',
  pageBuilder: (context, state) {
    return const DraggableNavigationSheetPage(child: _IntroPage());
  },
);

// ----------------------------------------------------------
// Pages
// ----------------------------------------------------------

class _Root extends StatelessWidget {
  const _Root();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: ElevatedButton(
        onPressed: () => context.go('/intro'),
        child: const Text('Generate Playlist'),
      ),
    ));
  }
}

class _SheetShell extends StatelessWidget {
  const _SheetShell({
    required this.transitionObserver,
    required this.navigator,
  });

  final NavigationSheetTransitionObserver transitionObserver;
  final Widget navigator;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      bottom: false,
      child: PopScope(
        canPop: false,
        onPopInvoked: (didPop) async {
          if (!didPop) {
            if (context.mounted) {
              context.go('/');
            }
          }
        },
        child: NavigationSheet(
          transitionObserver: sheetTransitionObserver,
          child: Material(
            // Add circular corners to the sheet.
            borderRadius: BorderRadius.circular(16),
            clipBehavior: Clip.antiAlias,
            color: Theme.of(context).colorScheme.surface,
            child: navigator,
          ),
        ),
      ),
    );
  }
}

class _IntroPage extends StatelessWidget {
  const _IntroPage();

  @override
  Widget build(BuildContext context) {
    return SheetContentScaffold(
      appBar: _SharedAppBarHero(
        appbar: AppBar(
          leading: IconButton(
            onPressed: () => context.go('/'),
            icon: const Icon(Icons.close),
          ),
        ),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 32,
            vertical: 8,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                "Hello there!\n"
                "I'm your AI music assistant. "
                "Ready to create the perfect playlist for you. 😊",
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.headlineMediumBold,
              ),
              const SizedBox(height: 64),
              FilledButton(
                onPressed: () => context.go('/intro/genre'),
                style: _largeFilledButtonStyle,
                child: const Text('Continue'),
              ),
              const SizedBox(height: 16),
              TextButton(
                onPressed: () => context.go('/'),
                style: _largeTextButtonStyle,
                child: const Text('No, thanks'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ----------------------------------------------------------
// Utilities
// ----------------------------------------------------------

extension on TextTheme {
  TextStyle? get headlineMediumBold => headlineMedium?.copyWith(fontWeight: FontWeight.bold);
}

final _largeFilledButtonStyle = FilledButton.styleFrom(
  minimumSize: const Size.fromHeight(56),
);

final _largeTextButtonStyle = TextButton.styleFrom(
  minimumSize: const Size.fromHeight(56),
);

class _SelectableChip extends StatefulWidget {
  const _SelectableChip({
    required this.label,
  });

  final Widget label;

  @override
  State<_SelectableChip> createState() => _SelectableChipState();
}

class _SelectableChipState extends State<_SelectableChip> {
  bool isSelected = false;

  @override
  Widget build(BuildContext context) {
    return FilterChip(
      onSelected: (isSelected) {
        setState(() => this.isSelected = isSelected);
      },
      selected: isSelected,
      label: widget.label,
    );
  }
}

class _SelectableListTile extends StatefulWidget {
  const _SelectableListTile({
    required this.title,
    required this.padding,
  });

  final String title;
  final EdgeInsets padding;

  @override
  State<_SelectableListTile> createState() => _SelectableListTileState();
}

class _SelectableListTileState extends State<_SelectableListTile> {
  bool isSelected = false;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: widget.padding,
      child: CheckboxListTile(
        title: Text(widget.title),
        value: isSelected,
        onChanged: (selected) {
          setState(() => isSelected = selected!);
        },
      ),
    );
  }
}

/// This widget makes it possible to create a (visually) shared appbar across the pages.
///
/// For better maintainability, it is recommended to create a page-specific app bar for each page
/// instead of a single 'super' shared app bar that includes all the functionality for every page.
class _SharedAppBarHero extends StatelessWidget implements PreferredSizeWidget {
  const _SharedAppBarHero({
    required this.appbar,
  });

  final AppBar appbar;

  @override
  Size get preferredSize => appbar.preferredSize;

  @override
  Widget build(BuildContext context) {
    return Hero(tag: 'HeroAppBar', child: appbar);
  }
}

@fujidaiti
Copy link
Owner

fujidaiti commented Jun 13, 2024

I created more simplified version and can reproduce the same problem. With the code below, I got another error in addition to the errors you reported:

'package:flutter/src/widgets/navigator.dart': Failed assertion: line 3577 pos 14: 'observer.navigator == null': is not true.

This indicates that a Navigator tried to attach itself to the sheetTransitionObserver , but it already had another navigator instance.

We have a problem that when a modal is currently closing and the user opens another one at the same time

As you mentioned, there is a time when two sheets exist, plus they share a globally defined NavigationObserver (sheetTransitionObserver). But Navigator doesn't allow to share an observer between multiple navigators as the error message suggests.

If your sheet directly creates a Navigator, we can fix this problem by moving the global navigation observer to a State:

class _SheetState extends State<_Sheet> {
  final sheetTransitionObserver = NavigationSheetTransitionObserver();

  @override
  Widget build(BuildContext context) {...}
}

But for go_router, there's no way to dynamically create observers per navigator. I don't have a good solution for this right now, but I'll take a closer look this weekend...

Simplified reproduction code
import 'package:flutter/material.dart';
import 'package:smooth_sheets/smooth_sheets.dart';

void main() {
  runApp(const _Issue152());
}

class _Issue152 extends StatelessWidget {
  const _Issue152();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Builder(
          builder: (context) {
            return SafeArea(
              child: Align(
                  alignment: Alignment.topCenter,
                  child: ElevatedButton(
                    onPressed: () => Navigator.of(context).push(
                      ModalSheetRoute(
                        builder: (context) => const _Sheet(),
                      ),
                    ),
                    child: const Text('Show Navigation Sheet'),
                  )),
            );
          },
        ),
      ),
    );
  }
}

final sheetTransitionObserver = NavigationSheetTransitionObserver();

class _Sheet extends StatelessWidget {
  const _Sheet();

  @override
  Widget build(BuildContext context) {
    final nestedNavigator = Navigator(
      observers: [sheetTransitionObserver],
      onGenerateRoute: (settings) {
        return DraggableNavigationSheetRoute(
          builder: (context) {
            return Container(
              width: double.infinity,
              height: 400,
              color: Colors.white,
            );
          },
        );
      },
    );

    return NavigationSheet(
      transitionObserver: _transitionObserver,
      child: Material(
        child: nestedNavigator,
      ),
    );
  }
}

@fujidaiti
Copy link
Owner

fujidaiti commented Jun 16, 2024

Related go_router issues:

I think we will have to wait for those issues to be fixed. The essential problem here is, there a time when temporarily two navigator instances exist in a frame, and the global key and the navigator observers are shared with those navigators, but which is not allowed, and current go_router doesn't provide a way to create a global key and navigator observers per navigator instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working P1
Projects
None yet
Development

No branches or pull requests

2 participants