-
Notifications
You must be signed in to change notification settings - Fork 22
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
Comments
Hi @kmorl, Can you post the code to reproduce this error? |
It is basically a minimal version of the 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);
}
} |
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
As you mentioned, there is a time when two sheets exist, plus they share a globally defined NavigationObserver ( If your sheet directly creates a 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 codeimport '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,
),
);
}
} |
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. |
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
The text was updated successfully, but these errors were encountered: