Implementing Routing and Deep Linking
Contents
- Core Concepts
- Workflow: Initializing the Application and Router
- Workflow: Configuring Platform Deep Linking
- Workflow: Implementing Nested Navigation
- Examples
Core Concepts
Use the go_router package for declarative routing in Flutter. It provides a robust API for complex routing scenarios, deep linking, and nested navigation.
- GoRouter: The central configuration object defining the application's route tree.
- GoRoute: A standard route mapping a URL path to a Flutter screen.
- ShellRoute / StatefulShellRoute: Wraps child routes in a persistent UI shell (e.g., a
BottomNavigationBar).StatefulShellRoutemaintains the state of parallel navigation branches. - Path URL Strategy: Removes the default
#fragment from web URLs, essential for clean deep linking across platforms.
Workflow: Initializing the Application and Router
Follow this workflow to bootstrap a new Flutter application with go_router and configure the root routing mechanism.
Task Progress
- Create the Flutter application.
- Add the
go_routerdependency. - Configure the URL strategy for web/deep linking.
- Implement the
GoRouterconfiguration. - Bind the router to
MaterialApp.router.
1. Scaffold the Application
Run the following commands to create the app and add the required routing package:
bashflutter create <app-name> cd <app-name> flutter pub add go_router
2. Configure the Router
Define a top-level GoRouter instance. Handle authentication or state-based routing using the redirect parameter.
dartimport 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; void main() { // Use path URL strategy to remove the '#' from web URLs usePathUrlStrategy(); runApp(const MyApp()); } final GoRouter _router = GoRouter( initialLocation: '/', routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!), ), ], ), ], errorBuilder: (context, state) => ErrorScreen(error: state.error), ); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, title: 'Routing App', ); } }
Workflow: Configuring Platform Deep Linking
Configure the native platforms to intercept specific URLs and route them into the Flutter application.
Task Progress
- Determine target platforms (iOS, Android, or both).
- Apply conditional configuration for Android (Manifest + Asset Links).
- Apply conditional configuration for iOS (Plist + Entitlements + AASA).
- Run validator -> review errors -> fix.
If configuring for Android:
- Modify
AndroidManifest.xml: Add the intent filter inside the<activity>tag for.MainActivity.
xml<intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="yourdomain.com" /> <data android:scheme="https" /> </intent-filter>
- Host
assetlinks.json: Serve the following JSON athttps://yourdomain.com/.well-known/assetlinks.json.
json[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.yourapp", "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"] } }]
If configuring for iOS:
- Modify
Info.plist: Opt-in to Flutter's default deep link handler. Note: If using a third-party deep linking plugin (e.g.,app_links), set this toNOto prevent conflicts.
xml<key>FlutterDeepLinkingEnabled</key> <true/>
- Modify
Runner.entitlements: Add the associated domain.
xml<key>com.apple.developer.associated-domains</key> <array> <string>applinks:yourdomain.com</string> </array>
- Host
apple-app-site-association: Serve the following JSON (without a.jsonextension) athttps://yourdomain.com/.well-known/apple-app-site-association.
json{ "applinks": { "apps": [], "details": [{ "appIDs": ["TEAM_ID.com.yourcompany.yourapp"], "paths": ["*"], "components": [{"/": "/*"}] }] } }
Validation Loop
Run validator -> review errors -> fix.
- Android: Test using ADB.
bash
adb shell 'am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourdomain.com/details/123"' com.yourcompany.yourapp - iOS: Test using
xcrunon a booted simulator.bashxcrun simctl openurl booted https://yourdomain.com/details/123
Workflow: Implementing Nested Navigation
Use StatefulShellRoute to implement persistent UI shells (like a bottom navigation bar) that maintain the state of their child routes.
Task Progress
- Define
StatefulShellRoute.indexedStackin theGoRouterconfiguration. - Create
StatefulShellBranchinstances for each navigation tab. - Implement the shell widget using
StatefulNavigationShell.
dartfinal GoRouter _router = GoRouter( initialLocation: '/home', routes: [ StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return ScaffoldWithNavBar(navigationShell: navigationShell); }, branches: [ StatefulShellBranch( routes: [ GoRoute( path: '/home', builder: (context, state) => const HomeScreen(), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/settings', builder: (context, state) => const SettingsScreen(), ), ], ), ], ), ], );
Examples
High-Fidelity Shell Widget Implementation
Implement the UI shell that consumes the StatefulNavigationShell to handle branch switching.
dartclass ScaffoldWithNavBar extends StatelessWidget { const ScaffoldWithNavBar({ required this.navigationShell, super.key, }); final StatefulNavigationShell navigationShell; void _goBranch(int index) { navigationShell.goBranch( index, // Support navigating to the initial location when tapping the active tab. initialLocation: index == navigationShell.currentIndex, ); } @override Widget build(BuildContext context) { return Scaffold( body: navigationShell, bottomNavigationBar: NavigationBar( selectedIndex: navigationShell.currentIndex, onDestinationSelected: _goBranch, destinations: const [ NavigationDestination(icon: Icon(Icons.home), label: 'Home'), NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'), ], ), ); } }
Programmatic Navigation
Use the context.go() and context.push() extension methods provided by go_router.
dart// Replaces the current route stack with the target route (Declarative) context.go('/details/123'); // Pushes the target route onto the existing stack (Imperative) context.push('/details/123'); // Navigates using a named route and path parameters context.goNamed('details', pathParameters: {'id': '123'}); // Pops the current route context.pop();


