Skip to content

Commit

Permalink
[go_router] Added top level onEnter callback.
Browse files Browse the repository at this point in the history
Added onEnter callback to enable route interception and demonstrate usage in example.
  • Loading branch information
omar-hanafy committed Dec 22, 2024
1 parent 3515aba commit 6be9a5d
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 47 deletions.
3 changes: 2 additions & 1 deletion packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

* 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;

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

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

0 comments on commit 6be9a5d

Please sign in to comment.