flutter-setup-declarative-routing

Configure `MaterialApp.router` en utilisant un paquet comme `go_router` pour la navigation avancée basée sur les URL. À utiliser lors du développement d'applications web ou d'applications mobiles qui nécessitent des liaisons profondes spécifiques et un support de l'historique du navigateur.

npx skills add https://github.com/flutter/skills --skill flutter-setup-declarative-routing

Implémentation du Routage et du Deep Linking

Contenu

Concepts fondamentaux

Utilisez le package go_router pour le routage déclaratif dans Flutter. Il fournit une API robuste pour les scénarios de routage complexes, le deep linking et la navigation imbriquée.

  • GoRouter : L'objet de configuration central définissant l'arborescence des routes de l'application.
  • GoRoute : Une route standard mappant un chemin URL à un écran Flutter.
  • ShellRoute / StatefulShellRoute : Enveloppe les routes enfants dans une coquille d'interface utilisateur persistante (par exemple, une BottomNavigationBar). StatefulShellRoute maintient l'état des branches de navigation parallèles.
  • Path URL Strategy : Supprime le fragment # par défaut des URL web, essentiel pour un deep linking propre entre les plateformes.

Workflow : Initialisation de l'application et du routeur

Suivez ce workflow pour amorcer une nouvelle application Flutter avec go_router et configurer le mécanisme de routage racine.

Progression des tâches

  • [ ] Créer l'application Flutter.
  • [ ] Ajouter la dépendance go_router.
  • [ ] Configurer la stratégie URL pour le web/deep linking.
  • [ ] Implémenter la configuration de GoRouter.
  • [ ] Lier le routeur à MaterialApp.router.

1. Créer l'application

Exécutez les commandes suivantes pour créer l'application et ajouter le package de routage requis :

flutter create <app-name>
cd <app-name>
flutter pub add go_router

2. Configurer le routeur

Définissez une instance GoRouter au niveau supérieur. Gérez le routage basé sur l'authentification ou l'état à l'aide du paramètre redirect.

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  // Utilisez la stratégie URL path pour supprimer le '#' des URL web
  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 : Configuration du deep linking par plateforme

Configurez les plateformes natives pour intercepter les URL spécifiques et les router vers l'application Flutter.

Progression des tâches

  • [ ] Déterminer les plateformes cibles (iOS, Android, ou les deux).
  • [ ] Appliquer la configuration conditionnelle pour Android (Manifest + Asset Links).
  • [ ] Appliquer la configuration conditionnelle pour iOS (Plist + Entitlements + AASA).
  • [ ] Exécuter le validateur -> examiner les erreurs -> corriger.

Si configuration pour Android :

  1. Modifier AndroidManifest.xml : Ajoutez le filtre d'intention à l'intérieur de la balise <activity> pour .MainActivity.
    <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>
  2. Héberger assetlinks.json : Servez le JSON suivant à https://yourdomain.com/.well-known/assetlinks.json.
    [{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
     "namespace": "android_app",
     "package_name": "com.yourcompany.yourapp",
     "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
    }
    }]

Si configuration pour iOS :

  1. Modifier Info.plist : Optez pour le gestionnaire de deep link par défaut de Flutter. Remarque : Si vous utilisez un plugin de deep linking tiers (par exemple, app_links), définissez ceci sur NO pour éviter les conflits.
    <key>FlutterDeepLinkingEnabled</key>
    <true/>
  2. Modifier Runner.entitlements : Ajoutez le domaine associé.
    <key>com.apple.developer.associated-domains</key>
    <array>
    <string>applinks:yourdomain.com</string>
    </array>
  3. Héberger apple-app-site-association : Servez le JSON suivant (sans extension .json) à https://yourdomain.com/.well-known/apple-app-site-association.
    {
    "applinks": {
     "apps": [],
     "details": [{
       "appIDs": ["TEAM_ID.com.yourcompany.yourapp"],
       "paths": ["*"],
       "components": [{"/": "/*"}]
     }]
    }
    }

Boucle de validation

Exécutez le validateur -> examinez les erreurs -> corrigez.

  • Android : Testez avec ADB.
    adb shell 'am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourdomain.com/details/123"' com.yourcompany.yourapp
  • iOS : Testez avec xcrun sur un simulateur en cours d'exécution.
    xcrun simctl openurl booted https://yourdomain.com/details/123

Workflow : Implémentation de la navigation imbriquée

Utilisez StatefulShellRoute pour implémenter des coquilles d'interface utilisateur persistantes (comme une barre de navigation inférieure) qui maintiennent l'état de leurs routes enfants.

Progression des tâches

  • [ ] Définir StatefulShellRoute.indexedStack dans la configuration de GoRouter.
  • [ ] Créer des instances StatefulShellBranch pour chaque onglet de navigation.
  • [ ] Implémenter le widget de coquille en utilisant StatefulNavigationShell.
final 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(),
            ),
          ],
        ),
      ],
    ),
  ],
);

Exemples

Implémentation haute fidélité du widget de coquille

Implémentez la coquille d'interface utilisateur qui consomme StatefulNavigationShell pour gérer le changement de branches.

class ScaffoldWithNavBar extends StatelessWidget {
  const ScaffoldWithNavBar({
    required this.navigationShell,
    super.key,
  });

  final StatefulNavigationShell navigationShell;

  void _goBranch(int index) {
    navigationShell.goBranch(
      index,
      // Supporte la navigation vers l'emplacement initial lors de l'appui sur l'onglet actif.
      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'),
        ],
      ),
    );
  }
}

Navigation programmatique

Utilisez les méthodes d'extension context.go() et context.push() fournies par go_router.

// Remplace la pile de routes actuelle par la route cible (Déclaratif)
context.go('/details/123');

// Ajoute la route cible à la pile existante (Impératif)
context.push('/details/123');

// Navigue en utilisant une route nommée et des paramètres de chemin
context.goNamed('details', pathParameters: {'id': '123'});

// Dépile la route actuelle
context.pop();