forked from flutter/packages
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR exposes the current top route to `GoRouterState`, this allows `ShellRoute` to know what is the current child and process the state accordingly. - Issue: flutter/flutter#140297 This could be used like this, given that each `GoRoute` had the `name` parameter given ```dart StatefulShellRoute.indexedStack( parentNavigatorKey: rootNavigatorKey, builder: ( BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell, ) { final String? routeName = GoRouterState.of(context).topRoute.name; final String title = switch (routeName) { 'a' => 'A', 'b' => 'B', _ => 'Unknown', }; return Column( children: <Widget>[ Text(title), Expanded(child: navigationShell), ], ); }, ... } ``` *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
- Loading branch information
1 parent
35ac30e
commit 516648a
Showing
8 changed files
with
431 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
305 changes: 305 additions & 0 deletions
305
packages/go_router/example/lib/shell_route_top_route.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// 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'; | ||
|
||
final GlobalKey<NavigatorState> _rootNavigatorKey = | ||
GlobalKey<NavigatorState>(debugLabel: 'root'); | ||
final GlobalKey<NavigatorState> _shellNavigatorKey = | ||
GlobalKey<NavigatorState>(debugLabel: 'shell'); | ||
|
||
// This scenario demonstrates how to set up nested navigation using ShellRoute, | ||
// which is a pattern where an additional Navigator is placed in the widget tree | ||
// to be used instead of the root navigator. This allows deep-links to display | ||
// pages along with other UI components such as a BottomNavigationBar. | ||
// | ||
// This example demonstrates how use topRoute in a ShellRoute to create the | ||
// title in the AppBar above the child, which is different for each GoRoute. | ||
|
||
void main() { | ||
runApp(ShellRouteExampleApp()); | ||
} | ||
|
||
/// An example demonstrating how to use [ShellRoute] | ||
class ShellRouteExampleApp extends StatelessWidget { | ||
/// Creates a [ShellRouteExampleApp] | ||
ShellRouteExampleApp({super.key}); | ||
|
||
final GoRouter _router = GoRouter( | ||
navigatorKey: _rootNavigatorKey, | ||
initialLocation: '/a', | ||
debugLogDiagnostics: true, | ||
routes: <RouteBase>[ | ||
/// Application shell | ||
ShellRoute( | ||
navigatorKey: _shellNavigatorKey, | ||
builder: (BuildContext context, GoRouterState state, Widget child) { | ||
final String? routeName = GoRouterState.of(context).topRoute?.name; | ||
// This title could also be created using a route's path parameters in GoRouterState | ||
final String title = switch (routeName) { | ||
'a' => 'A Screen', | ||
'a.details' => 'A Details', | ||
'b' => 'B Screen', | ||
'b.details' => 'B Details', | ||
'c' => 'C Screen', | ||
'c.details' => 'C Details', | ||
_ => 'Unknown', | ||
}; | ||
return ScaffoldWithNavBar(title: title, child: child); | ||
}, | ||
routes: <RouteBase>[ | ||
/// The first screen to display in the bottom navigation bar. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'a', | ||
path: '/a', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const ScreenA(); | ||
}, | ||
routes: <RouteBase>[ | ||
// The details screen to display stacked on the inner Navigator. | ||
// This will cover screen A but not the application shell. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'a.details', | ||
path: 'details', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const DetailsScreen(label: 'A'); | ||
}, | ||
), | ||
], | ||
), | ||
|
||
/// Displayed when the second item in the the bottom navigation bar is | ||
/// selected. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'b', | ||
path: '/b', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const ScreenB(); | ||
}, | ||
routes: <RouteBase>[ | ||
// The details screen to display stacked on the inner Navigator. | ||
// This will cover screen B but not the application shell. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'b.details', | ||
path: 'details', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const DetailsScreen(label: 'B'); | ||
}, | ||
), | ||
], | ||
), | ||
|
||
/// The third screen to display in the bottom navigation bar. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'c', | ||
path: '/c', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const ScreenC(); | ||
}, | ||
routes: <RouteBase>[ | ||
// The details screen to display stacked on the inner Navigator. | ||
// This will cover screen C but not the application shell. | ||
GoRoute( | ||
// The name of this route used to determine the title in the ShellRoute. | ||
name: 'c.details', | ||
path: 'details', | ||
builder: (BuildContext context, GoRouterState state) { | ||
return const DetailsScreen(label: 'C'); | ||
}, | ||
), | ||
], | ||
), | ||
], | ||
), | ||
], | ||
); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return MaterialApp.router( | ||
title: 'Flutter Demo', | ||
theme: ThemeData( | ||
primarySwatch: Colors.blue, | ||
), | ||
routerConfig: _router, | ||
); | ||
} | ||
} | ||
|
||
/// Builds the "shell" for the app by building a Scaffold with a | ||
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. | ||
class ScaffoldWithNavBar extends StatelessWidget { | ||
/// Constructs an [ScaffoldWithNavBar]. | ||
const ScaffoldWithNavBar({ | ||
super.key, | ||
required this.title, | ||
required this.child, | ||
}); | ||
|
||
/// The title to display in the AppBar. | ||
final String title; | ||
|
||
/// The widget to display in the body of the Scaffold. | ||
/// In this sample, it is a Navigator. | ||
final Widget child; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: child, | ||
appBar: AppBar( | ||
title: Text(title), | ||
leading: _buildLeadingButton(context), | ||
), | ||
bottomNavigationBar: BottomNavigationBar( | ||
items: const <BottomNavigationBarItem>[ | ||
BottomNavigationBarItem( | ||
icon: Icon(Icons.home), | ||
label: 'A Screen', | ||
), | ||
BottomNavigationBarItem( | ||
icon: Icon(Icons.business), | ||
label: 'B Screen', | ||
), | ||
BottomNavigationBarItem( | ||
icon: Icon(Icons.notification_important_rounded), | ||
label: 'C Screen', | ||
), | ||
], | ||
currentIndex: _calculateSelectedIndex(context), | ||
onTap: (int idx) => _onItemTapped(idx, context), | ||
), | ||
); | ||
} | ||
|
||
/// Builds the app bar leading button using the current location [Uri]. | ||
/// | ||
/// The [Scaffold]'s default back button cannot be used because it doesn't | ||
/// have the context of the current child. | ||
Widget? _buildLeadingButton(BuildContext context) { | ||
final RouteMatchList currentConfiguration = | ||
GoRouter.of(context).routerDelegate.currentConfiguration; | ||
final RouteMatch lastMatch = currentConfiguration.last; | ||
final Uri location = lastMatch is ImperativeRouteMatch | ||
? lastMatch.matches.uri | ||
: currentConfiguration.uri; | ||
final bool canPop = location.pathSegments.length > 1; | ||
return canPop ? BackButton(onPressed: GoRouter.of(context).pop) : null; | ||
} | ||
|
||
static int _calculateSelectedIndex(BuildContext context) { | ||
final String location = GoRouterState.of(context).uri.toString(); | ||
if (location.startsWith('/a')) { | ||
return 0; | ||
} | ||
if (location.startsWith('/b')) { | ||
return 1; | ||
} | ||
if (location.startsWith('/c')) { | ||
return 2; | ||
} | ||
return 0; | ||
} | ||
|
||
void _onItemTapped(int index, BuildContext context) { | ||
switch (index) { | ||
case 0: | ||
GoRouter.of(context).go('/a'); | ||
case 1: | ||
GoRouter.of(context).go('/b'); | ||
case 2: | ||
GoRouter.of(context).go('/c'); | ||
} | ||
} | ||
} | ||
|
||
/// The first screen in the bottom navigation bar. | ||
class ScreenA extends StatelessWidget { | ||
/// Constructs a [ScreenA] widget. | ||
const ScreenA({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Center( | ||
child: TextButton( | ||
onPressed: () { | ||
GoRouter.of(context).go('/a/details'); | ||
}, | ||
child: const Text('View A details'), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// The second screen in the bottom navigation bar. | ||
class ScreenB extends StatelessWidget { | ||
/// Constructs a [ScreenB] widget. | ||
const ScreenB({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Center( | ||
child: TextButton( | ||
onPressed: () { | ||
GoRouter.of(context).go('/b/details'); | ||
}, | ||
child: const Text('View B details'), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// The third screen in the bottom navigation bar. | ||
class ScreenC extends StatelessWidget { | ||
/// Constructs a [ScreenC] widget. | ||
const ScreenC({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Center( | ||
child: TextButton( | ||
onPressed: () { | ||
GoRouter.of(context).go('/c/details'); | ||
}, | ||
child: const Text('View C details'), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
/// The details screen for either the A, B or C screen. | ||
class DetailsScreen extends StatelessWidget { | ||
/// Constructs a [DetailsScreen]. | ||
const DetailsScreen({ | ||
required this.label, | ||
super.key, | ||
}); | ||
|
||
/// The label to display in the center of the screen. | ||
final String label; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
body: Center( | ||
child: Text( | ||
'Details for $label', | ||
style: Theme.of(context).textTheme.headlineMedium, | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.