Flutter Navigation 2.0 en GoRouter in 2026: Deep Linking en Sollicitatievragen

Beheers Flutter-navigatie met GoRouter 17.5: declaratieve routing, deep linking, ShellRoute, route guards en veelgestelde sollicitatievragen met praktische voorbeelden.

Flutter-navigatiearchitectuur met GoRouter deep linking-stroomdiagram

Flutter-navigatie is aanzienlijk geëvolueerd sinds de introductie van Navigation 2.0. GoRouter 17.5, het officiële pakket van het Flutter-team, handelt de complexiteit van de Router API af en biedt standaard declaratieve routing, automatische deep linking en geneste navigatie.

GoRouter is feature-complete

Het Flutter-team beschouwt GoRouter als feature-complete sinds 2026. Het ondersteunt padparameters, queryparameters, redirects, ShellRoute voor persistente UI en StatefulShellRoute voor het behouden van tab-status. Raw Navigator 2.0 wordt zelden meer handmatig geïmplementeerd in productie.

De architectuur van Navigator 2.0 begrijpen

Navigator 2.0 introduceerde een declaratieve, URL-gestuurde benadering voor Flutter-navigatie. In plaats van imperatieve push- en pop-aanroepen bepaalt de app-status wat er op de navigatiestack verschijnt. De architectuur steunt op drie kernklassen:

  • Router: De top-level widget die navigatie coördineert
  • RouteInformationParser: Vertaalt URLs naar app-status
  • RouterDelegate: Bouwt de widget-boom op basis van die status

Wanneer een deep link van het besturingssysteem binnenkomt of een URL verandert in een webbrowser, werkt het framework de status bij en reageert de UI. Het probleem: het handmatig schrijven van een aangepaste RouterDelegate en RouteInformationParser vereist aanzienlijke boilerplate.

raw_navigator2_example.dartdart
// Raw Navigator 2.0 approach - verbose and rarely used directly
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
  
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  AppRoutePath? _currentPath;

  
  AppRoutePath? get currentConfiguration => _currentPath;

  
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        const MaterialPage(child: HomeScreen()),
        if (_currentPath?.isProductPage == true)
          MaterialPage(child: ProductScreen(id: _currentPath!.productId!)),
      ],
      onDidRemovePage: (page) {
        // Handle page removal
      },
    );
  }

  
  Future<void> setNewRoutePath(AppRoutePath path) async {
    _currentPath = path;
    notifyListeners();
  }
}

Deze verbositeit is de reden waarom GoRouter bestaat. Het kapselt Navigator 2.0 in en handelt de boilerplate af.

GoRouter 17.5 configureren

GoRouter 17.5 introduceert ondersteuning voor route-metadata en reguliere expressies als beperkingen voor padparameters. De minimumvereiste is Flutter 3.32 en Dart 3.8.

router_config.dartdart
import 'package:go_router/go_router.dart';

final goRouter = GoRouter(
  initialLocation: '/',
  debugLogDiagnostics: true, // Useful during development
  routes: [
    GoRoute(
      path: '/',
      name: 'home',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'products/:id', // Path parameter
          name: 'product',
          builder: (context, state) {
            final productId = state.pathParameters['id']!;
            return ProductScreen(productId: productId);
          },
        ),
        GoRoute(
          path: 'search',
          name: 'search',
          builder: (context, state) {
            // Query parameters: /search?q=flutter&category=books
            final query = state.uri.queryParameters['q'] ?? '';
            final category = state.uri.queryParameters['category'];
            return SearchScreen(query: query, category: category);
          },
        ),
      ],
    ),
  ],
);

De router integreert met MaterialApp.router:

main.dartdart
import 'package:flutter/material.dart';
import 'router_config.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: goRouter,
      title: 'GoRouter Demo',
    );
  }
}

Deep Linking-configuratie voor Android en iOS

Deep linking maakt het mogelijk dat externe URLs specifieke schermen in de app openen. GoRouter handelt de routing automatisch af zodra de platformconfiguratie aanwezig is.

Android vereist een intent-filter in AndroidManifest.xml en een Digital Asset Links-bestand dat op het domein wordt gehost:

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".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="https"
      android:host="example.com"
      android:pathPrefix="/products" />
  </intent-filter>
</activity>

Het assetlinks.json-bestand moet worden geserveerd op https://example.com/.well-known/assetlinks.json.

Voor iOS moet de Associated Domains-capability worden ingeschakeld in Xcode, en een apple-app-site-association-bestand moet worden gehost:

https://example.com/.well-known/apple-app-site-associationjson
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.example.app",
        "paths": ["/products/*", "/search"]
      }
    ]
  }
}

GoRouter 17.5 heeft een kritieke bug opgelost waarbij Android cold-start deep links met lege paden het schema en authority verloren. Dit betekent dat deep links nu betrouwbaar werken, zelfs wanneer de app niet draait.

Deep links testen

Test Android deep links met adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123". Voor iOS: xcrun simctl openurl booted "https://example.com/products/123".

ShellRoute voor persistente navigatie-UI

ShellRoute omhult child-routes met een persistent UI-element zoals een BottomNavigationBar of Drawer. De shell blijft zichtbaar tijdens het navigeren tussen zijn kinderen.

shell_router_config.dartdart
final goRouter = GoRouter(
  initialLocation: '/home',
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithNavBar(child: child);
      },
      routes: [
        GoRoute(
          path: '/home',
          name: 'home',
          builder: (context, state) => const HomeTab(),
        ),
        GoRoute(
          path: '/explore',
          name: 'explore',
          builder: (context, state) => const ExploreTab(),
        ),
        GoRoute(
          path: '/profile',
          name: 'profile',
          builder: (context, state) => const ProfileTab(),
        ),
      ],
    ),
  ],
);

// scaffold_with_nav_bar.dart
class ScaffoldWithNavBar extends StatelessWidget {
  final Widget child;
  const ScaffoldWithNavBar({super.key, required this.child});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _calculateSelectedIndex(context),
        onTap: (index) => _onItemTapped(index, context),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.explore), label: 'Explore'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }

  int _calculateSelectedIndex(BuildContext context) {
    final location = GoRouterState.of(context).uri.toString();
    if (location.startsWith('/explore')) return 1;
    if (location.startsWith('/profile')) return 2;
    return 0;
  }

  void _onItemTapped(int index, BuildContext context) {
    switch (index) {
      case 0: context.go('/home');
      case 1: context.go('/explore');
      case 2: context.go('/profile');
    }
  }
}

GoRouter 17.5 heeft een probleem opgelost waarbij iOS-teruggebaren de gehele ShellRoute popten in plaats van alleen de actieve sub-route.

Klaar om je Flutter gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

StatefulShellRoute voor het behouden van tab-status

Wanneer gebruikers van tab wisselen, herbouwt ShellRoute het kind. StatefulShellRoute behoudt de navigatiestack van elke branch:

stateful_shell_config.dartdart
final goRouter = GoRouter(
  initialLocation: '/home',
  routes: [
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        return ScaffoldWithNavBar(navigationShell: navigationShell);
      },
      branches: [
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/home',
              builder: (context, state) => const HomeTab(),
              routes: [
                GoRoute(
                  path: 'details/:id',
                  builder: (context, state) => DetailsScreen(
                    id: state.pathParameters['id']!,
                  ),
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/explore',
              builder: (context, state) => const ExploreTab(),
            ),
          ],
        ),
        StatefulShellBranch(
          routes: [
            GoRoute(
              path: '/profile',
              builder: (context, state) => const ProfileTab(),
            ),
          ],
        ),
      ],
    ),
  ],
);

Met deze configuratie: als een gebruiker naar /home/details/42 navigeert, naar de Explore-tab wisselt en dan terugkeert naar Home, bevindt hij zich nog steeds op het detailscherm.

Route Guards met Redirect

Redirects handelen authenticatiecontroles en conditionele navigatie af. De redirect-callback wordt voor elke navigatie uitgevoerd:

authenticated_router.dartdart
final goRouter = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    final isLoggedIn = AuthService.instance.isLoggedIn;
    final isLoggingIn = state.matchedLocation == '/login';

    // Redirect to login if not authenticated
    if (!isLoggedIn && !isLoggingIn) {
      return '/login?redirect=${state.uri}';
    }

    // Redirect away from login if already authenticated
    if (isLoggedIn && isLoggingIn) {
      final redirect = state.uri.queryParameters['redirect'];
      return redirect ?? '/';
    }

    return null; // No redirect
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginScreen(),
    ),
    GoRoute(
      path: '/admin',
      redirect: (context, state) {
        // Route-level redirect for admin-only access
        if (!AuthService.instance.isAdmin) {
          return '/';
        }
        return null;
      },
      builder: (context, state) => const AdminScreen(),
    ),
  ],
);

GoRouter 17.5 heeft ondersteuning toegevoegd voor het gebruik van context-extensiemethoden zoals context.namedLocation() binnen redirect-callbacks.

Veelgestelde Flutter-navigatie sollicitatievragen

Interviewers testen regelmatig navigatiekennis omdat het architectuur, state management en platformintegratie raakt. Hier zijn vragen die senior kandidaten onderscheiden:

V: Welk probleem lost Navigator 2.0 op dat Navigator 1.0 niet oplost?

Navigator 1.0 gebruikt imperatieve navigatie: Navigator.push() en Navigator.pop(). De app kan de navigatiestatus niet als URL representeren, wat deep linking en webondersteuning bemoeilijkt. Navigator 2.0 is declaratief: de app-status bepaalt de navigatiestack, URLs zijn eersteklas burgers, en deep links werken automatisch.

V: Wanneer zou men context.go() vs context.push() gebruiken?

context.go('/path') vervangt de huidige navigatiestack tot aan de gematchte route. context.push('/path') voegt een nieuwe route toe bovenop de bestaande stack. Men gebruikt go() voor top-level navigatie (tabs wisselen, naar home gaan) en push() voor het dieper ingaan op details terwijl de terug-navigatie behouden blijft.

V: Hoe behoudt men status tussen tab-wisselingen?

Men gebruikt StatefulShellRoute.indexedStack(). Het onderhoudt aparte navigatiestacks voor elke branch en behoudt ze bij het wisselen van tabs. Zonder dit wordt bij het wisselen van tabs de gehele sub-boom herbouwd.

V: Hoe geeft men complexe objecten door tussen routes?

Er bestaan drie benaderingen. Ten eerste, een ID doorgeven in de padparameter en het object ophalen op het bestemmingsscherm. Ten tweede, GoRouters extra-parameter gebruiken om het object direct door te geven. Ten derde, het object opslaan in een state management-oplossing (Riverpod, Bloc) en het vanuit de bestemming benaderen. De eerste benadering is het beste voor deep linking omdat de URL deelbaar blijft.

dart
// Passing via extra (not deep-linkable)
context.push('/product', extra: productObject);

// Receiving via extra
GoRoute(
  path: '/product',
  builder: (context, state) {
    final product = state.extra as Product?;
    return ProductScreen(product: product);
  },
),

V: Wat is de onEnter-callback in GoRouter 17.5?

De onEnter-callback wordt uitgevoerd bij het betreden van een route en biedt toegang tot zowel de huidige als de volgende route-status. Het maakt het mogelijk om logica uit te voeren voordat de route volledig laadt, nuttig voor analytics of conditioneel voorladen.

Type-safe routes met code-generatie

GoRouter ondersteunt type-safe routing via code-generatie. Men definieert route-dataklassen en laat build_runner de navigatiecode genereren:

routes.dartdart
import 'package:go_router/go_router.dart';

part 'routes.g.dart';

<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
  const HomeRoute();

  
  Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}

<ProductRoute>(path: '/product/:id')
class ProductRoute extends GoRouteData {
  final String id;
  const ProductRoute({required this.id});

  
  Widget build(BuildContext context, GoRouterState state) =>
      ProductScreen(productId: id);
}

Na het uitvoeren van dart run build_runner build kan met compile-time veiligheid worden genavigeerd:

dart
// Type-safe navigation
ProductRoute(id: '123').go(context);

Dit elimineert typefouten in routepaden en zorgt ervoor dat parameters altijd worden verstrekt.

Flutter-navigatie voor productie-apps beheersen

  • GoRouter 17.5 is de aanbevolen navigatieoplossing voor Flutter-apps die deep linking, geneste navigatie of webondersteuning vereisen
  • Men gebruikt ShellRoute voor persistente bottom navigation bars; men gebruikt StatefulShellRoute om de navigatiestack van elke tab te behouden
  • Platformspecifieke deep linking (Android App Links, iOS Universal Links) wordt apart van GoRouter geconfigureerd, dat de routing-logica afhandelt
  • Authenticatie-guards worden geïmplementeerd met de redirect-callback op router-niveau of individueel route-niveau
  • Voor sollicitatiescenario's ligt de focus op het uitleggen van het declaratieve model: app-status stuurt de UI, URLs worden afgeleid van de status, en GoRouter handelt de vertaling af
  • Type-safe routes met code-generatie elimineren runtime-fouten door verkeerd getypte paden
  • Raadpleeg de Flutter-navigatiedocumentatie en de GoRouter changelog voor versie-specifieke features

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in Flutter?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 23 augustus 2026

Tags

#flutter
#navigation
#gorouter
#deep-linking
#mobile

Delen

Gerelateerde artikelen