From c6695598438141e9d9c76bc783fa08bd6503f0e4 Mon Sep 17 00:00:00 2001 From: David Miguel Date: Thu, 16 Jul 2026 14:24:27 +0200 Subject: [PATCH 1/2] [go_router] Add BlockedInitialNavigationException for blocked initial deep links When Block.stop() is returned from onEnter with no prior route configuration (e.g. an initial deep link), the parser now raises a typed BlockedInitialNavigationException instead of a generic GoException, so apps can distinguish this case in onException without string matching. --- packages/go_router/lib/src/misc/errors.dart | 11 +++++++ packages/go_router/lib/src/parser.dart | 2 +- .../change_2026_07_16_1784204325728.yaml | 3 ++ packages/go_router/test/on_enter_test.dart | 31 +++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 packages/go_router/pending_changelogs/change_2026_07_16_1784204325728.yaml diff --git a/packages/go_router/lib/src/misc/errors.dart b/packages/go_router/lib/src/misc/errors.dart index e272dea1474b..31705cd13e77 100644 --- a/packages/go_router/lib/src/misc/errors.dart +++ b/packages/go_router/lib/src/misc/errors.dart @@ -25,3 +25,14 @@ class GoException implements Exception { @override String toString() => 'GoException: $message'; } + +/// Raised when [Block] is returned from `onEnter` but there is no prior +/// route configuration to restore (e.g., an initial deep link was blocked). +/// +/// Apps can check for this specific type in `onException` to recover +/// gracefully (e.g., redirect to a loading screen with the deep link +/// preserved) instead of treating it as a generic routing error. +class BlockedInitialNavigationException extends GoException { + /// Creates an exception for a blocked initial navigation attempt. + BlockedInitialNavigationException(super.message); +} diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index 7ed750b4c3f0..5bb1c05d1e57 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -132,7 +132,7 @@ class GoRouteInformationParser extends RouteInformationParser { // Surface an error so the app decides how to recover via onException. final RouteMatchList blocked = _OnEnterHandler._errorRouteMatchList( effectiveRoute.uri, - GoException( + BlockedInitialNavigationException( 'Navigation to ${effectiveRoute.uri} was blocked by onEnter with no prior route to restore', ), extra: infoState.extra, diff --git a/packages/go_router/pending_changelogs/change_2026_07_16_1784204325728.yaml b/packages/go_router/pending_changelogs/change_2026_07_16_1784204325728.yaml new file mode 100644 index 000000000000..3aac8b4552a7 --- /dev/null +++ b/packages/go_router/pending_changelogs/change_2026_07_16_1784204325728.yaml @@ -0,0 +1,3 @@ +changelog: | + - Adds `BlockedInitialNavigationException` (a `GoException` subtype), raised when the initial navigation is blocked by `onEnter` with no prior route to restore, so apps can distinguish this case in `onException` without string matching. +version: minor diff --git a/packages/go_router/test/on_enter_test.dart b/packages/go_router/test/on_enter_test.dart index 2c068bf16160..7a9603597690 100644 --- a/packages/go_router/test/on_enter_test.dart +++ b/packages/go_router/test/on_enter_test.dart @@ -868,6 +868,37 @@ void main() { expect(find.text('Fallback Page'), findsOneWidget); }); + testWidgets('Should raise BlockedInitialNavigationException when the initial navigation ' + 'is blocked by onEnter with no prior route to restore', (WidgetTester tester) async { + Object? capturedError; + + router = GoRouter( + initialLocation: '/protected', + onEnter: + ( + BuildContext context, + GoRouterState current, + GoRouterState next, + GoRouter goRouter, + ) async { + if (next.uri.path == '/protected') { + return const Block.stop(); + } + return const Allow(); + }, + onException: (BuildContext context, GoRouterState state, GoRouter goRouter) { + capturedError = state.error; + }, + routes: [GoRoute(path: '/protected', builder: (_, _) => const Placeholder())], + ); + + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + expect(capturedError, isA()); + expect(capturedError, isA()); + }); + testWidgets('onEnter has priority over deprecated redirect', (WidgetTester tester) async { var redirectCallCount = 0; var onEnterCallCount = 0; From e837c65b4976cb6bac2d32cb17f2c6ffdeef37f1 Mon Sep 17 00:00:00 2001 From: David Miguel Date: Thu, 16 Jul 2026 15:04:47 +0200 Subject: [PATCH 2/2] Use code font for Block doc reference --- packages/go_router/lib/src/misc/errors.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go_router/lib/src/misc/errors.dart b/packages/go_router/lib/src/misc/errors.dart index 31705cd13e77..0863e46c72b9 100644 --- a/packages/go_router/lib/src/misc/errors.dart +++ b/packages/go_router/lib/src/misc/errors.dart @@ -26,7 +26,7 @@ class GoException implements Exception { String toString() => 'GoException: $message'; } -/// Raised when [Block] is returned from `onEnter` but there is no prior +/// Raised when `Block` is returned from `onEnter` but there is no prior /// route configuration to restore (e.g., an initial deep link was blocked). /// /// Apps can check for this specific type in `onException` to recover