Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6be9a5d
[go_router] Added top level onEnter callback.
omar-hanafy Dec 22, 2024
171b639
added version 14.7.0
omar-hanafy Dec 22, 2024
f52a269
Merge branch 'main' into main
omar-hanafy Dec 25, 2024
3bbd241
Merge branch 'main' into main
omar-hanafy Dec 27, 2024
6a60006
Merge branch 'main' into main
omar-hanafy Jan 3, 2025
d1e1fc2
[go_router] added nextState, and currentState to OnEnter signature, a…
omar-hanafy Jan 24, 2025
516db13
Merge branch 'main' into main
omar-hanafy Jan 24, 2025
e1f10b1
Merge branch 'main' into main
omar-hanafy Jan 26, 2025
7a847b8
Merge branch 'main' into main
omar-hanafy Jan 28, 2025
b08d804
Merge branch 'main' into main
omar-hanafy Feb 2, 2025
1e25466
Merge branch 'main' into main
omar-hanafy Feb 4, 2025
aec8e47
Add router instance to OnEnter callback
omar-hanafy Feb 4, 2025
2bdc147
Merge branch 'main' into main
omar-hanafy Feb 8, 2025
1bd3c18
[go_router] Async onEnter, improved redirection, and loop prevention.
omar-hanafy Feb 15, 2025
61729b2
Merge branch 'main' into main
omar-hanafy Feb 15, 2025
8334a64
Merge branch 'main' into main
omar-hanafy Feb 17, 2025
f28337e
improved redirection and async handling.
omar-hanafy Feb 18, 2025
4092405
extracting the onEnter logic into its own helper class.
omar-hanafy Feb 18, 2025
c1c09d0
added named params to handleTopOnEnter.
omar-hanafy Feb 18, 2025
d9e6ea6
move tests
cedvdb Feb 20, 2025
07c15f0
Merge pull request #3 from cedvdb/move_tests
omar-hanafy Feb 22, 2025
3fbe011
Merge branch 'main' into main
omar-hanafy Mar 28, 2025
67df52a
added tests
cedvdb Apr 6, 2025
eef39b1
Merge pull request #4 from cedvdb/omar-add-on-enter-tests
omar-hanafy Apr 7, 2025
359eb0e
Merge branch 'flutter:main' into main
omar-hanafy Apr 7, 2025
cc57519
[go_router] Fix onEnter callback exception handling and enhance tests
omar-hanafy Apr 8, 2025
d4f2416
[go_router] updated Should allow redirection with query parameters te…
omar-hanafy Apr 9, 2025
56f2dbe
Merge branch 'main' into main
omar-hanafy Apr 16, 2025
c458982
Merge branch 'main' into main
omar-hanafy Apr 28, 2025
921dcb3
Merge branch 'main' into main
omar-hanafy May 30, 2025
757f5a1
[go_router] Use specific imports in on_enter.dart
omar-hanafy May 30, 2025
b5e1e9e
Merge branch 'main' of https://github.com/omar-hanafy/packages
omar-hanafy May 30, 2025
86c506b
Merge branch 'main' into main
omar-hanafy Jun 10, 2025
3c4a85f
Merge branch 'main' into main
omar-hanafy Jun 11, 2025
9d52c0d
Merge branch 'main' into main
omar-hanafy Jun 18, 2025
97c5ed8
Merge branch 'main' into main
omar-hanafy Jun 24, 2025
0323a45
Merge branch 'main' into main
omar-hanafy Jul 9, 2025
4a9e6ff
[go_router] Replace boolean return with sealed class API for onEnter
omar-hanafy Jul 9, 2025
3b2df49
[go_router] Make OnEnterHandler private and ensure onEnter priority o…
omar-hanafy Aug 2, 2025
1dd95d8
Merge branch 'main' into main
omar-hanafy Aug 2, 2025
ade3f54
Merge branch 'main' into main
omar-hanafy Aug 19, 2025
ed43b0b
Merge branch 'main' into main
omar-hanafy Aug 19, 2025
d0d5e6d
[go_router] added allow/block factories for the OnEnterResult
omar-hanafy Aug 19, 2025
a484965
[go_router] ran dart format
omar-hanafy Aug 19, 2025
10404f8
[go_router] returned back missing docs in GoRouterRedirect
omar-hanafy Aug 19, 2025
2b3d0be
[go_router] Fix license headers to match repository standards
omar-hanafy Aug 19, 2025
b554e6c
[go_router] sealed `onEnter` (Allow/Block.then) + compose legacy redi…
omar-hanafy Aug 20, 2025
99ab3c3
[go_router] Fix license headers to match repository standards
omar-hanafy Aug 20, 2025
265f26f
[go_router] Refactor parser and on_enter for clarity and type safety
omar-hanafy Aug 22, 2025
01f7ea6
[go_router] Remove unused initialLocation parameter from parser.
omar-hanafy Aug 22, 2025
22a29bd
[go_router] Ensure onEnter runs during state restoration, and unified…
omar-hanafy Aug 27, 2025
887c528
Merge branch 'main' into main
omar-hanafy Aug 28, 2025
37712e5
Merge branch 'main' into main
omar-hanafy Aug 30, 2025
ee55004
Merge branch 'main' into main
omar-hanafy Sep 21, 2025
9e37af9
[go_router] polish onEnter handling and docs
omar-hanafy Sep 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 14.9.0
- Adds new top level `onEnter` callback with access to current and next route states.
- Deprecates top level `redirect` in favor of `onEnter`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually mark deprecation in package. instead, we just break the api and bump major version.

for this case though I think we should hold off deprecation until we have a better way to migrate the old behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the deprecation wording from the CHANGELOG and switched to calling the old behavior “legacy” in docs. I also removed the @Deprecated annotations from the public redirect API to avoid surfacing warnings before we have a clear migration path.


## 14.8.0

- Adds `preload` parameter to `StatefulShellBranchData.$branch`.
Expand Down
360 changes: 360 additions & 0 deletions packages/go_router/example/lib/top_level_on_enter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
// Copyright 2013 The Flutter Authors.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

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

/// Simulated service for handling referrals and deep links
class ReferralService {
/// processReferralCode
static Future<bool> processReferralCode(String code) async {
// Simulate network delay
await Future<dynamic>.delayed(const Duration(seconds: 1));
return true;
}

/// trackDeepLink
static Future<void> trackDeepLink(Uri uri) async {
// Simulate analytics tracking
await Future<dynamic>.delayed(const Duration(milliseconds: 300));
debugPrint('Deep link tracked: $uri');
}
}

void main() => runApp(const App());

/// The main application widget.
class App extends StatelessWidget {
/// The main application widget.
const App({super.key});

@override
Widget build(BuildContext context) {
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();

return MaterialApp.router(
routerConfig: _router(key),
title: 'Top-level onEnter',
theme: ThemeData(
useMaterial3: true,
primarySwatch: Colors.blue,
),
);
}

/// Configures the router with navigation handling and deep link support.
GoRouter _router(GlobalKey<NavigatorState> key) {
return GoRouter(
navigatorKey: key,
initialLocation: '/home',
debugLogDiagnostics: true,

/// Handles incoming routes before navigation occurs.
/// This callback can:
/// 1. Block navigation and perform actions (return false)
/// 2. Allow navigation to proceed (return true)
/// 3. Show loading states during async operations
onEnter: (
BuildContext context,
GoRouterState currentState,
GoRouterState nextState,
GoRouter goRouter,
) {
// Track analytics for deep links
if (nextState.uri.hasQuery || nextState.uri.hasFragment) {
_handleDeepLinkTracking(nextState.uri);
}

// Handle special routes
switch (nextState.uri.path) {
case '/referral':
_handleReferralDeepLink(context, nextState);
return false; // Prevent navigation

case '/auth':
if (nextState.uri.queryParameters['token'] != null) {
_handleAuthCallback(context, nextState);
return false; // Prevent navigation
}
return true;

default:
return true; // Allow navigation for all other routes
}
},
routes: <RouteBase>[
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/settings',
builder: (BuildContext context, GoRouterState state) =>
const SettingsScreen(),
),
// Add route for testing purposes, but it won't navigate
GoRoute(
path: '/referral',
builder: (BuildContext context, GoRouterState state) =>
const SizedBox(), // Never reached
),
],
);
}

/// Handles tracking of deep links asynchronously
void _handleDeepLinkTracking(Uri uri) {
ReferralService.trackDeepLink(uri).catchError((dynamic error) {
debugPrint('Failed to track deep link: $error');
});
}

/// Processes referral deep links with loading state
void _handleReferralDeepLink(BuildContext context, GoRouterState state) {
final String? code = state.uri.queryParameters['code'];
if (code == null) {
return;
}

// Show loading immediately
showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => const Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Processing referral...'),
],
),
),
),
),
);

// Process referral asynchronously
ReferralService.processReferralCode(code).then(
(bool success) {
if (!context.mounted) {
return;
}

// Close loading dialog
Navigator.of(context).pop();

// Show result
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success
? 'Referral code $code applied successfully!'
: 'Failed to apply referral code',
),
),
);
},
onError: (dynamic error) {
if (!context.mounted) {
return;
}

// Close loading dialog
Navigator.of(context).pop();

// Show error
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $error'),
backgroundColor: Colors.red,
),
);
},
);
}

/// Handles OAuth callback processing
void _handleAuthCallback(BuildContext context, GoRouterState state) {
final String token = state.uri.queryParameters['token']!;

// Show processing state
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Processing authentication...'),
duration: Duration(seconds: 1),
),
);

// Process auth token asynchronously
// Replace with your actual auth logic
Future<void>(() async {
await Future<dynamic>.delayed(const Duration(seconds: 1));
if (!context.mounted) {
return;
}

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Processed auth token: $token'),
),
);
}).catchError((dynamic error) {
if (!context.mounted) {
return;
}

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Auth error: $error'),
backgroundColor: Colors.red,
),
);
});
}
}

/// Demonstrates various navigation scenarios and deep link handling.
class HomeScreen extends StatelessWidget {
/// Demonstrates various navigation scenarios and deep link handling.
const HomeScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Top-level onEnter'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => context.go('/settings'),
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Navigation examples
ElevatedButton.icon(
onPressed: () => context.go('/login'),
icon: const Icon(Icons.login),
label: const Text('Go to Login'),
),
const SizedBox(height: 16),

// Deep link examples
Text('Deep Link Tests',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
const _DeepLinkButton(
label: 'Process Referral',
path: '/referral?code=TEST123',
description: 'Processes code without navigation',
),
const SizedBox(height: 8),
const _DeepLinkButton(
label: 'Auth Callback',
path: '/auth?token=abc123',
description: 'Simulates OAuth callback',
),
],
),
),
);
}
}

/// A button that demonstrates a deep link scenario.
class _DeepLinkButton extends StatelessWidget {
const _DeepLinkButton({
required this.label,
required this.path,
required this.description,
});

final String label;
final String path;
final String description;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
OutlinedButton(
onPressed: () => context.go(path),
child: Text(label),
),
Text(
description,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
],
),
);
}
}

/// Login screen implementation
class LoginScreen extends StatelessWidget {
/// Login screen implementation

const LoginScreen({super.key});

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton.icon(
onPressed: () => context.go('/home'),
icon: const Icon(Icons.home),
label: const Text('Go to Home'),
),
],
),
),
);
}

/// Settings screen implementation
class SettingsScreen extends StatelessWidget {
/// Settings screen implementation
const SettingsScreen({super.key});

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
ListTile(
title: const Text('Home'),
leading: const Icon(Icons.home),
onTap: () => context.go('/home'),
),
ListTile(
title: const Text('Login'),
leading: const Icon(Icons.login),
onTap: () => context.go('/login'),
),
],
),
);
}
Loading