Deep Linking & Declarative Routing
Modern Flutter navigation requires declarative URL routing (go_router built on Navigator 2.0) and seamless Deep Linking via iOS Universal Links and Android App Links.
1. Deep Linking Standards: Custom Schemes vs. Universal/App Links
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEEP LINKING STANDARDS β
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Custom URL Schemes β 2. Universal Links (iOS) & App Links (Android) β
βββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β - Scheme: `myapp://product/1` β - Domain: `https://example.com/product/1` β
β - No ownership verification β - Requires domain ownership verification β
β - Vulnerable to scheme hijackingβ - `apple-app-site-association` & `assetlinks.json` β
β - Shows fallback popups β - Opens app directly without browser prompt β
βββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββDomain Verification Protocol
To verify domain ownership:
- iOS Universal Links: Host
apple-app-site-associationathttps://example.com/.well-known/apple-app-site-association. - Android App Links: Host
assetlinks.jsonathttps://example.com/.well-known/assetlinks.jsoncontaining the appβs SHA-256 fingerprint.
2. Imperative (Navigator 1.0) vs. Declarative (GoRouter)
| Feature | Imperative (Navigator 1.0) | Declarative (GoRouter / Router API) |
|---|---|---|
| Model | Navigator.push() stack mutation | UI = f(Location) (URL is state) |
| Deep Link Handling | Fragile manual route parsing | Native first-class URL route matching |
| Web Navigation | Poor browser URL bar integration | Perfect browser history & URL syncing |
| Nested Navigation | Complex nested Navigator keys | ShellRoute / StatefulShellRoute |
3. Declarative Routing with GoRouter
GoRouter is the official Flutter package wrapping Navigator 2.0.
import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter(
initialLocation: '/',
// Global Redirect Guard (e.g. Authentication Check)
redirect: (context, state) {
final bool isLoggedIn = authService.isLoggedIn;
final bool isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/dashboard';
return null; // No redirect needed
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
// Dynamic Path Parameter Route
GoRoute(
path: '/product/:id',
builder: (context, state) {
final productId = state.pathParameters['id']!;
return ProductDetailScreen(id: productId);
},
),
],
);go() vs push() in GoRouter
context.go('/product/123'): Replaces the navigation stack to match the exact target URI hierarchy.context.push('/product/123'): Pushes a new page onto the existing navigation stack without altering URI path history.
4. Persistent Bottom Navigation (StatefulShellRoute)
StatefulShellRoute.indexedStack maintains separate navigation stacks for each tab in a bottom navigation bar, preserving scroll state and widget trees across tab switches:
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
currentIndex: navigationShell.currentIndex,
onTap: (index) => navigationShell.goBranch(index),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
},
branches: [
StatefulShellBranch(routes: [GoRoute(path: '/home', builder: (_, __) => const HomeTab())]),
StatefulShellBranch(routes: [GoRoute(path: '/profile', builder: (_, __) => const ProfileTab())]),
],
);5. Trade-offs & Production Considerations
- Server Hosting Dependency: If the web server hosting
apple-app-site-associationorassetlinks.jsongoes down or returns invalid JSON headers, domain verification fails, reverting deep links to browser web pages. - Query Parameter Validation: Deep link parameters (
/product/:id?promo=xyz) come from external web links. Always sanitize and validate incoming path parameters before querying local databases or APIs.