-
Notifications
You must be signed in to change notification settings - Fork 29.5k
Open
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.35Found to occur in 3.35Found to occur in 3.35found in release: 3.36Found to occur in 3.36Found to occur in 3.36has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team
Description
Steps to reproduce
- Create two similar routes and enable
debugLogDiagnosticsin your GoRouter config:
// Generated route
@TypedGoRoute<MyGeneratedRoute>(path: '/generated')
class MyGeneratedRoute extends GoRouteData {
@override
Widget build(BuildContext context, GoRouterState state) {
return MyPage();
}
}
// Manual route
GoRoute(
path: '/manual',
builder: (context, state) => MyPage(),
// Note: no onExit callback
)- Navigate to each route and then pop back
- Observe the timing difference in when route configuration updates occur and the popping and restoring logs.
Expected results
Both generated and manual routes should have consistent timing for route configuration updates during navigation operations.
Actual results
There's a significant behavioral difference between routes created with GoRouteData.$route (generated routes) and manually defined GoRoutes in how they handle route popping, specifically in the _handlePopPageWithRouteMatch method. This leads to different timing for when _completeRouteMatch is called, which can cause issues with state management and route configuration updates. Mostly on pop() where restore(currentConfiguration)will be called before _completeRouteMatch execution.
Root Cause Analysis
In delegate.dart at line 152-171, the logic branches based on whether routeBase.onExit == null:
bool _handlePopPageWithRouteMatch(
Route<Object?> route,
Object? result,
RouteMatchBase match,
) {
// ... willHandlePopInternally check ...
final RouteBase routeBase = match.route;
if (routeBase is! GoRoute || routeBase.onExit == null) {
// MANUAL ROUTES: Execute immediately
route.didPop(result);
_completeRouteMatch(result, match); // ← Synchronous execution
return true;
}
// GENERATED ROUTES: Always go through this path
scheduleMicrotask(() async {
final bool onExitResult = await routeBase.onExit!(
navigatorKey.currentContext!,
match.buildState(_configuration, currentConfiguration),
);
if (onExitResult) {
_completeRouteMatch(result, match); // ← Asynchronous execution
}
});
return false;
}Code sample
main.dart
void main() {
runApp(
ProviderScope(
child: MaterialApp.router(
routerConfig: router,
),
),
);
}
class GroupsPage extends ConsumerWidget {
const GroupsPage({super.key, required this.groupId});
final String groupId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: Text('Group $groupId'),
actions: [
IconButton(
onPressed: () => context.go('/groups/$groupId/settings'),
icon: const Icon(Icons.settings),
),
],
),
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => ListTile(
onTap: () => context.go('/groups/$groupId/posts/$index'),
tileColor: [Colors.red.shade300, Colors.blue.shade300][index % 2],
title: Text('Post of group $groupId n°$index'),
),
),
);
}
}
class PostPage extends ConsumerWidget {
const PostPage({super.key, required this.postId});
final String postId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.arrow_back),
),
title: Text('Post $postId'),
),
body: Text('This is the content of post $postId'),
);
}
}
class SettingsPage extends ConsumerWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.arrow_back),
),
),
body: const Center(
child: Column(
children: [
Text('Settings'),
],
),
),
);
}
}
router.dart
part 'router.g.dart';
final router = GoRouter(
debugLogDiagnostics: true,
initialLocation: '/groups/1',
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, child) => Scaffold(
appBar: AppBar(
title: const Text('Test App'),
),
body: child,
),
branches: [
StatefulShellBranch(
routes: [
ShellRoute(
builder: (context, state, child) => Scaffold(
appBar: AppBar(
title: const Text('Group List'),
),
body: child,
),
routes: [
GoRoute(
path: '/groups/empty',
builder: (context, state) => const GroupsPage(
groupId: 'all',
),
redirect: (context, state) => '/groups/1',
),
GoRoute(
path: '/groups/:id',
builder: (context, state) => GroupsPage(
groupId: state.pathParameters['id']!,
),
routes: [
PostDetailPage.route,
GoRoute(
path: 'settings',
builder: (context, state) => const SettingsPage(),
),
],
),
],
),
],
)
],
)
],
);
@TypedGoRoute<PostDetailPage>(path: 'posts/:id')
class PostDetailPage extends GoRouteData with _$PostDetailPage {
const PostDetailPage({required this.id});
static final route = $postDetailPage;
final String id;
@override
Widget build(BuildContext context, GoRouterState state) {
return PostPage(
postId: id,
);
}
}Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[GoRouter] Full paths for routes:
└─ (ShellRoute)
└─ (ShellRoute)
├─/groups/empty (GroupsPage)
└─/groups/:id (GroupsPage)
├─/groups/:id/posts/:id (Widget)
└─/groups/:id/settings (SettingsPage)
[GoRouter] setting initial location /groups/1
[GoRouter] Using MaterialApp configuration
[GoRouter] going to /groups/1/settings
[GoRouter] popping /groups/1/settings
[GoRouter] restoring /groups/1
[GoRouter] going to /groups/1/posts/0
[GoRouter] popping /groups/1/posts/0
[GoRouter] restoring /groups/1/posts/0Flutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.35.1, on macOS 15.4.1 24E263 darwin-arm64, locale fr-FR) [277ms]
• Flutter version 3.35.1 on channel stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 20f8274939 (13 days ago), 2025-08-14 10:53:09 -0700
• Engine revision 1e9a811bf8
• Dart version 3.9.0
• DevTools version 2.48.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
enable-ios, cli-animations, enable-lldb-debuggingMetadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.35Found to occur in 3.35Found to occur in 3.35found in release: 3.36Found to occur in 3.36Found to occur in 3.36has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team