Description
Steps to Reproduce
See the code below, or this repo.
Expected results:
I would expect didPush
in NavigatorObserver
to be called for every navigation to any page.
Actual results:
It doesn't get called at all, except for the page b/details
possibly because it's using a _rootNavigatorKey
?
So in the app, if you click on each tab, and each detail page, (and back again), you only see two logs:
[MyNavObserver] didPush: route(/b/details: {}), previousRoute= route(: {})
[MyNavObserver] didPop: route(/b/details: {}), previousRoute= route(: {})
Code sample
// 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';
import 'package:logging/logging.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 to display a route within a ShellRoute and also
// push a screen using a different navigator (such as the root Navigator) by
// providing a `parentNavigatorKey`.
void main() {
runApp(ShellRouteExampleApp());
}
/// An example demonstrating how to use [ShellRoute]
class ShellRouteExampleApp extends StatelessWidget {
/// Creates a [ShellRouteExampleApp]
ShellRouteExampleApp({Key? key}) : super(key: key);
final GoRouter _router = GoRouter(
observers: <NavigatorObserver>[MyNavObserver()],
debugLogDiagnostics: true,
navigatorKey: _rootNavigatorKey,
initialLocation: '/a',
routes: <RouteBase>[
/// Application shell
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldWithNavBar(child: child);
},
routes: <RouteBase>[
/// The first screen to display in the bottom navigation bar.
GoRoute(
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(
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(
path: '/b',
builder: (BuildContext context, GoRouterState state) {
return const ScreenB();
},
routes: <RouteBase>[
/// Same as "/a/details", but displayed on the root Navigator by
/// specifying [parentNavigatorKey]. This will cover both screen B
/// and the application shell.
GoRoute(
path: 'details',
parentNavigatorKey: _rootNavigatorKey,
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'B');
},
),
],
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Defmo',
theme: ThemeData(
primarySwatch: Colors.red,
),
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
routeInformationProvider: _router.routeInformationProvider,
);
}
}
/// 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({
required this.child,
Key? key,
}) : super(key: key);
/// 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,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'A Screen',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'B Screen',
),
],
currentIndex: _calculateSelectedIndex(context),
onTap: (int idx) => _onItemTapped(idx, context),
),
);
}
static int _calculateSelectedIndex(BuildContext context) {
final GoRouter route = GoRouter.of(context);
final String location = route.location;
if (location == '/a') {
return 0;
}
if (location == '/b') {
return 1;
}
return 0;
}
void _onItemTapped(int index, BuildContext context) {
switch (index) {
case 0:
GoRouter.of(context).go('/a');
break;
case 1:
GoRouter.of(context).go('/b');
break;
}
}
}
/// The first screen in the bottom navigation bar.
class ScreenA extends StatelessWidget {
/// Constructs a [ScreenA] widget.
const ScreenA({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Screen A'),
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({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Screen B'),
TextButton(
onPressed: () {
GoRouter.of(context).go('/b/details');
},
child: const Text('View B details'),
),
],
),
),
);
}
}
/// The details screen for either the A or B screen.
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
Key? key,
}) : super(key: key);
/// The label to display in the center of the screen.
final String label;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details Screen'),
),
body: Center(
child: Text(
'Details for $label',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
/// The Navigator observer.
class MyNavObserver extends NavigatorObserver {
/// Creates a [MyNavObserver].
MyNavObserver() {
log.onRecord.listen((LogRecord e) => debugPrint('$e'));
}
/// The logged message.
final Logger log = Logger('MyNavObserver');
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
log.info('didPush: ${route.str}, previousRoute= ${previousRoute?.str}');
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) =>
log.info('didPop: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) =>
log.info('didRemove: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) =>
log.info('didReplace: new= ${newRoute?.str}, old= ${oldRoute?.str}');
}
extension on Route<dynamic> {
String get str => 'route(${settings.name}: ${settings.arguments})';
}
flutter doctor -v
[✓] Flutter (Channel stable, 3.3.2, on macOS 12.6 21G115 darwin-x64, locale en-GB) • Flutter version 3.3.2 on channel stable at /Users/jonmountjoy/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision e3c29ec (8 days ago), 2022-09-14 08:46:55 -0500 • Engine revision a4ff2c53d8 • Dart version 2.18.1 • DevTools version 2.15.0[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
• Android SDK at /Users/jonmountjoy/Library/Android/sdk
• Platform android-33, build-tools 30.0.3
• ANDROID_HOME = /Users/jonmountjoy/Library/Android/sdk
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 14.0)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 14A309
• CocoaPods version 1.11.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome