Flutter Navigation 2.0 dan GoRouter di 2026: Deep Linking dan Pertanyaan Interview

Panduan lengkap navigasi Flutter dengan GoRouter 17.5: routing deklaratif, deep linking, ShellRoute, route guards, dan pertanyaan interview dengan contoh kode.

Flutter Navigation 2.0 dan GoRouter di 2026: Deep Linking dan Pertanyaan Interview

Navigasi Flutter telah berkembang secara signifikan sejak diperkenalkannya Navigation 2.0. GoRouter 17.5, paket resmi dari tim Flutter, kini menangani kompleksitas Router API sambil menyediakan routing deklaratif, deep linking otomatis, dan nested navigation secara out of the box.

GoRouter sudah feature-complete

Tim Flutter menganggap GoRouter sudah feature-complete per 2026. Paket ini mendukung path parameters, query parameters, redirects, ShellRoute untuk UI persisten, dan StatefulShellRoute untuk mempertahankan state tab. Navigator 2.0 mentah jarang diimplementasikan secara manual di production.

Memahami Arsitektur Navigator 2.0

Navigator 2.0 memperkenalkan pendekatan deklaratif dan berbasis URL untuk navigasi Flutter. Alih-alih panggilan imperatif push dan pop, state aplikasi menentukan apa yang muncul di navigation stack. Arsitektur ini bergantung pada tiga kelas inti:

  • Router: Widget tingkat atas yang mengkoordinasikan navigasi
  • RouteInformationParser: Menerjemahkan URL menjadi state aplikasi
  • RouterDelegate: Membangun widget tree berdasarkan state tersebut

Ketika deep link tiba dari OS atau URL berubah di browser web, framework memperbarui state, dan UI bereaksi. Masalahnya: menulis RouterDelegate dan RouteInformationParser kustom secara manual membutuhkan boilerplate yang signifikan.

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();
  }
}

Verbositas inilah mengapa GoRouter ada. GoRouter membungkus Navigator 2.0 dan menangani boilerplate tersebut.

Menyiapkan GoRouter 17.5

GoRouter 17.5 memperkenalkan dukungan route metadata dan regular expression constraints untuk path parameters. Persyaratan SDK minimum adalah Flutter 3.32 dan 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);
          },
        ),
      ],
    ),
  ],
);

Router ini terintegrasi dengan 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',
    );
  }
}

Konfigurasi Deep Linking untuk Android dan iOS

Deep linking memungkinkan URL eksternal membuka layar tertentu di aplikasi. GoRouter menangani routing secara otomatis setelah konfigurasi platform selesai.

Android membutuhkan intent-filter di AndroidManifest.xml dan file Digital Asset Links yang di-host di domain:

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>

File assetlinks.json harus disajikan di https://example.com/.well-known/assetlinks.json.

Untuk iOS, kemampuan Associated Domains harus diaktifkan di Xcode, dan file apple-app-site-association harus di-host:

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

GoRouter 17.5 memperbaiki bug kritis di mana deep link cold-start Android dengan path kosong kehilangan scheme dan authority. Ini berarti deep link sekarang berfungsi dengan andal bahkan ketika aplikasi tidak berjalan.

Menguji deep links

Uji deep links Android dengan adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123". Untuk iOS, gunakan xcrun simctl openurl booted "https://example.com/products/123".

ShellRoute untuk UI Navigasi Persisten

ShellRoute membungkus child routes dengan elemen UI persisten seperti BottomNavigationBar atau Drawer. Shell tetap terlihat saat bernavigasi antar children-nya.

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 memperbaiki masalah di mana iOS back gestures akan mem-pop seluruh ShellRoute alih-alih hanya sub-route aktif.

Siap menguasai wawancara Flutter Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

StatefulShellRoute untuk Mempertahankan State Tab

Ketika pengguna berpindah tab, ShellRoute membangun ulang child. StatefulShellRoute mempertahankan navigation stack dari setiap 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(),
            ),
          ],
        ),
      ],
    ),
  ],
);

Dengan setup ini, jika pengguna bernavigasi ke /home/details/42, beralih ke tab Explore, kemudian kembali ke Home, mereka akan tetap berada di layar details.

Route Guards dengan Redirect

Redirects menangani pemeriksaan autentikasi dan navigasi kondisional. Callback redirect berjalan sebelum setiap navigasi:

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 menambahkan dukungan untuk menggunakan context extension methods seperti context.namedLocation() dalam redirect callbacks.

Pertanyaan Interview Navigasi Flutter yang Umum

Pewawancara sering menguji pengetahuan navigasi karena menyentuh arsitektur, state management, dan integrasi platform. Berikut pertanyaan yang membedakan kandidat senior:

Q: Masalah apa yang dipecahkan Navigator 2.0 yang tidak bisa dilakukan Navigator 1.0?

Navigator 1.0 menggunakan navigasi imperatif: Navigator.push() dan Navigator.pop(). Aplikasi tidak dapat merepresentasikan state navigasi sebagai URL, membuat deep linking dan dukungan web sulit. Navigator 2.0 bersifat deklaratif: state aplikasi menentukan navigation stack, URL adalah first-class citizens, dan deep links bekerja secara otomatis.

Q: Kapan menggunakan context.go() vs context.push()?

context.go('/path') menggantikan navigation stack saat ini hingga route yang cocok. context.push('/path') menambahkan route baru di atas stack yang ada. Gunakan go() untuk navigasi top-level (berpindah tab, ke home) dan push() untuk masuk ke detail sambil mempertahankan navigasi kembali.

Q: Bagaimana cara mempertahankan state saat berpindah tab?

Gunakan StatefulShellRoute.indexedStack(). Ini mempertahankan navigation stacks terpisah untuk setiap branch dan mempertahankannya saat berpindah tab. Tanpanya, berpindah tab membangun ulang seluruh sub-tree.

Q: Bagaimana cara passing objek kompleks antar route?

Ada tiga pendekatan. Pertama, pass ID di path parameter dan fetch objek di layar tujuan. Kedua, gunakan parameter extra GoRouter untuk passing objek langsung. Ketiga, simpan objek di solusi state management (Riverpod, Bloc) dan akses dari tujuan. Pendekatan pertama terbaik untuk deep linking karena URL tetap bisa dibagikan.

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);
  },
),

Q: Apa itu callback onEnter di GoRouter 17.5?

Callback onEnter dieksekusi saat memasuki route dan menyediakan akses ke state route saat ini dan selanjutnya. Ini memungkinkan menjalankan logika sebelum route sepenuhnya dimuat, berguna untuk analytics atau conditional pre-loading.

Type-Safe Routes dengan Code Generation

GoRouter mendukung type-safe routing melalui code generation. Definisikan route data classes dan biarkan build_runner menghasilkan navigation code:

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);
}

Setelah menjalankan dart run build_runner build, navigasi dengan compile-time safety:

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

Ini menghilangkan typo di route paths dan memastikan parameters selalu disediakan.

Menguasai Navigasi Flutter untuk Aplikasi Production

  • GoRouter 17.5 adalah solusi navigasi yang direkomendasikan untuk aplikasi Flutter yang membutuhkan deep linking, nested navigation, atau dukungan web
  • Gunakan ShellRoute untuk bottom navigation bars persisten; gunakan StatefulShellRoute untuk mempertahankan navigation stack setiap tab
  • Konfigurasi deep linking spesifik platform (Android App Links, iOS Universal Links) secara terpisah dari GoRouter, yang menangani logika routing
  • Implementasikan authentication guards dengan callback redirect di level router atau level route individual
  • Untuk skenario interview, fokus pada penjelasan model deklaratif: state aplikasi mendorong UI, URL diturunkan dari state, dan GoRouter menangani terjemahannya
  • Type-safe routes dengan code generation menghilangkan runtime errors dari mistyped paths
  • Periksa dokumentasi navigasi Flutter dan changelog GoRouter untuk fitur spesifik versi

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Flutter?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 23 Agustus 2026

Bagikan

Artikel terkait