I’m working on a Flutter application where I have a main Navigator
and a nested Navigator
within a SetupFlow
widget. The URL updates correctly for the first-level routes but does not change for the second-level routes within SetupFlow
.
Here is my setup:
const routeHome = '/';
const routeSettings = '/settings';
const routePrefixDeviceSetup = '/setup/';
const routeDeviceSetupStartPage = 'find_devices';
const routeDeviceSetupSelectDevicePage = 'select_device';
const routeDeviceSetupConnectingPage = 'connecting';
const routeDeviceSetupFinishedPage = 'finished';
void main() {
runApp(
MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
appBarTheme: const AppBarTheme(backgroundColor: Colors.blue),
floatingActionButtonTheme: const FloatingActionButtonThemeData(backgroundColor: Colors.blue),
),
onGenerateRoute: (settings) {
late Widget page;
if (settings.name == routeHome) {
page = const HomeScreen();
} else if (settings.name == routeSettings) {
page = const SettingsScreen();
} else if (settings.name!.startsWith(routePrefixDeviceSetup)) {
final subRoute = settings.name!.substring(routePrefixDeviceSetup.length);
page = SetupFlow(setupPageRoute: subRoute);
} else {
throw Exception('Unknown route: ${settings.name}');
}
return MaterialPageRoute<dynamic>(
builder: (context) { return page; },
settings: settings,
);
},
debugShowCheckedModeBanner: false,
),
);
}
@immutable
class SetupFlow extends StatefulWidget {
const SetupFlow({super.key, required this.setupPageRoute});
final String setupPageRoute;
@override
SetupFlowState createState() => SetupFlowState();
}
class SetupFlowState extends State<SetupFlow> {
final _navigatorKey = GlobalKey<NavigatorState>();
void _onDiscoveryComplete() {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage);
}
void _onDeviceSelected(String deviceId) {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage);
}
void _onConnectionEstablished() {
_navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage);
}
Future<void> _onExitPressed() async {
final isConfirmed = await _isExitDesired();
if (isConfirmed && mounted) { _exitSetup(); }
}
Future<bool> _isExitDesired() async {
return await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Are you sure?'),
content: const Text('If you exit device setup, your progress will be lost.'),
actions: [
TextButton(onPressed: () { Navigator.of(context).pop(true); }, child: const Text('Leave')),
TextButton(onPressed: () { Navigator.of(context).pop(false); }, child: const Text('Stay')),
],
);
}) ?? false;
}
void _exitSetup() {
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (didPop) async {
if (didPop) return;
if (await _isExitDesired() && context.mounted) { _exitSetup(); }
},
child: Scaffold(
appBar: _buildFlowAppBar(),
body: Navigator(
key: _navigatorKey,
initialRoute: widget.setupPageRoute,
onGenerateRoute: (settings) {
late Widget page;
switch (settings.name) {
case routeDeviceSetupStartPage:
page = WaitingPage(message: 'Searching for nearby bulb...', onWaitComplete: _onDiscoveryComplete);
break;
case routeDeviceSetupSelectDevicePage:
page = SelectDevicePage(onDeviceSelected: _onDeviceSelected);
break;
case routeDeviceSetupConnectingPage:
page = WaitingPage(message: 'Connecting...', onWaitComplete: _onConnectionEstablished);
break;
case routeDeviceSetupFinishedPage:
page = FinishedPage(onFinishPressed: _exitSetup);
break;
default:
throw Exception('Unknown route: ${settings.name}');
}
return MaterialPageRoute<dynamic>(builder: (context) { return page; }, settings: settings);
},
),
),
);
}
PreferredSizeWidget _buildFlowAppBar() {
return AppBar(
leading: IconButton(onPressed: _onExitPressed, icon: const Icon(Icons.chevron_left)),
title: const Text('Bulb Setup'),
);
}
}
The URL changes for the first-level routes (routeHome, routeSettings) but not for the second-level routes within SetupFlow. How can I ensure the URL updates correctly for the nested Navigator as well?