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

[go_router] Support for top level onEnter callback. #8339

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
5 changes: 3 additions & 2 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT
## 14.7.0

* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
- Updated the minimum supported SDK version to Flutter 3.22/Dart 3.4.
- Added new top level `onEnter` callback for controlling incoming route navigation.

## 14.6.2

Expand Down
154 changes: 154 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,154 @@
// 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';

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

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

/// The title of the app.
static const String title = 'GoRouter Example: Top-level onEnter';

@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: GoRouter(
initialLocation: '/home',

/// A callback invoked for every route navigation attempt.
///
/// If the callback returns `false`, the navigation is blocked.
/// Use this to handle authentication, referrals, or other route-based logic.
onEnter: (BuildContext context, GoRouterState state) {
// Save the referral code (if provided) and block navigation to the /referral route.
if (state.uri.path == '/referral') {
saveReferralCode(context, state.uri.queryParameters['code']);
return false;
}

return true; // Allow navigation for all other routes.
},

/// The list of application routes.
routes: <GoRoute>[
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(),
),
],
),
title: title,
);
}

/// The login screen widget.
class LoginScreen extends StatelessWidget {
/// Constructs a [LoginScreen].
const LoginScreen({super.key});

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

/// The home screen widget.
class HomeScreen extends StatelessWidget {
/// Constructs a [HomeScreen].
const HomeScreen({super.key});

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/login'),
child: const Text('Go to Login'),
),
ElevatedButton(
onPressed: () => context.go('/settings'),
child: const Text('Go to Settings'),
),
ElevatedButton(
// This would typically be triggered by an incoming deep link.
onPressed: () => context.go('/referral?code=12345'),
child: const Text('Save Referral Code'),
),
],
),
),
);
}

/// The settings screen widget.
class SettingsScreen extends StatelessWidget {
/// Constructs a [SettingsScreen].
const SettingsScreen({super.key});

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

/// Saves a referral code.
///
/// Displays a [SnackBar] with the referral code for demonstration purposes.
/// Replace this with real referral handling logic.
void saveReferralCode(BuildContext context, String? code) {
if (code != null) {
// Here you can implement logic to save the referral code as needed.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Referral code saved: $code')),
);
}
}
33 changes: 33 additions & 0 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ import 'state.dart';
typedef GoRouterRedirect = FutureOr<String?> Function(
BuildContext context, GoRouterState state);

/// The signature of the onEnter callback.
typedef OnEnter = bool Function(BuildContext context, GoRouterState state);

/// The route configuration for GoRouter configured by the app.
class RouteConfiguration {
/// Constructs a [RouteConfiguration].
RouteConfiguration(
this._routingConfig, {
required this.navigatorKey,
this.extraCodec,
this.onEnter,
}) {
_onRoutingTableChanged();
_routingConfig.addListener(_onRoutingTableChanged);
Expand Down Expand Up @@ -246,6 +250,35 @@ class RouteConfiguration {
/// example.
final Codec<Object?, Object?>? extraCodec;

/// A callback invoked for every incoming route before it is processed.
///
/// This callback allows you to control navigation by inspecting the incoming
/// route and conditionally preventing the navigation. If the callback returns
/// `true`, the GoRouter proceeds with the regular navigation and redirection
/// logic. If the callback returns `false`, the navigation is canceled.
///
/// When a deep link opens the app and `onEnter` returns `false`, GoRouter
/// will automatically redirect to the initial route or '/'.
///
/// Example:
/// ```dart
/// final GoRouter router = GoRouter(
/// routes: [...],
/// onEnter: (BuildContext context, Uri uri) {
/// if (uri.path == '/login' && isUserLoggedIn()) {
/// return false; // Prevent navigation to /login
/// }
/// if (uri.path == '/referral') {
/// // Save the referral code and prevent navigation
/// saveReferralCode(uri.queryParameters['code']);
/// return false;
/// }
/// return true; // Allow navigation
/// },
/// );
/// ```
final OnEnter? onEnter;
omar-hanafy marked this conversation as resolved.
Show resolved Hide resolved

final Map<String, String> _nameToPath = <String, String>{};

/// Looks up the url location by a [GoRoute]'s name.
Expand Down
Loading
Loading