Routing Modern dengan go_router
Tujuan Pembelajaran
Setelah mengikuti pembelajaran ini, siswa mampu:
1. Memahami cara kerja perpindahan halaman di Flutter
2. Menjelaskan konsep URL dan sub-URL dalam routing
3. Mengirim data antar halaman dengan beberapa cara
4. Mengelola navigasi halaman dengan go_router
5. Menerapkan praktik routing yang rapi dan umum digunakan di industri
Pertanyaan Pemantik
- Pernahkah kalian membuka aplikasi lalu tiba-tiba kembali ke halaman login tanpa alasan jelas?
- Menurut kalian, bagaimana aplikasi besar mengatur alur pindah halaman agar tidak berantakan?
Diskusikan singkat selama 5 menit.
1. Konsep Perpindahan Halaman di Flutter
Pada Flutter, perpindahan halaman bukan mengganti halaman lama, tetapi menumpuk halaman baru di atas halaman lama.
Contoh dengan Navigator klasik:
Navigator.push(context, MaterialPageRoute(builder: (_) => PageB()));
Yang terjadi:
- Halaman A masih ada di memori
- Halaman B ditumpuk di atasnya
- Tombol back akan menghapus (pop) halaman B
Perumpamaan:
Tumpukan buku.
Buku baru diletakkan di atas, bukan mengganti buku lama.
Masalah:
- Jika aplikasi besar, push/pop tersebar
- Sulit mengatur alur login, logout, dan deep link
2. Routing Berbasis URL
go_router memperkenalkan cara berpikir berbasis URL, mirip aplikasi web.
Contoh:
- /products → halaman daftar produk
- /products/detail → halaman detail produk
- /products/123 → detail produk dengan ID tertentu
Dengan URL:
- Setiap halaman punya alamat jelas
- Mudah untuk deep link dan debugging
3. Struktur URL: URL Inti dan Sub URL
Contoh struktur yang sering dipakai:
- URL inti: /products
- Sub URL: /products/:id
Artinya:
- /products menampilkan daftar produk
- /products/:id menampilkan detail satu produk
Struktur ini disebut nested route dan umum dipakai di perusahaan.
4. Pengiriman Data Antar Halaman
A. Mengirim 1 Data (ID / String / Angka)
Biasanya menggunakan path parameter.
Contoh:
- URL: /products/123
- Data yang dikirim: 123 (ID produk)
Digunakan untuk:
- deep link
- data kecil dan penting
B. Mengirim Banyak Data (Object / List)
Menggunakan extra.
Catatan penting:
- extra tidak terlihat di URL
- Cocok untuk data kompleks
- Untuk web, tetap kombinasikan dengan ID
C. Query Parameter
Contoh:
- /products?category=audio&sort=price
Digunakan untuk:
- filter
- sorting
- pencarian
5. Cara Pindah Halaman di go_router
A. go()
Digunakan untuk:
- pindah menu utama
- reset alur halaman
- setelah login / logout
B. push()
Digunakan untuk:
- masuk ke halaman detail
- halaman yang wajar untuk kembali (back)
C. replace()
Digunakan untuk:
- mengganti halaman saat ini
- mencegah user kembali ke halaman sebelumnya
6. Kembali ke Halaman Sebelumnya
context.pop();
7. Mengirim Data Kembali ke Halaman Sebelumnya
Digunakan untuk:
- form input
- halaman edit data
Data bisa berupa:
- boolean
- string
- object
8. Best Practice Routing di Dunia Kerja
- Routing terpusat di satu file
- Jangan hardcode path
- Gunakan path parameter untuk deep link
- Bedakan penggunaan
godanpush - Sediakan halaman error (404)
- Konsisten menggunakan satu sistem routing
Persiapan
Gunakan project dari Pertemuan 1:
smk_product_app
1. Menambahkan Dependency go_router
Buka file pubspec.yaml dan tambahkan:
dependencies:
flutter:
sdk: flutter
go_router: ^14.0.0
Jalankan perintah:
flutter pub get
2. Menyiapkan Struktur Routing
Buat folder dan file berikut:
lib/
├── routes/
│ ├── app_routes.dart
│ └── app_router.dart
├── pages/
├── widgets/
├── models/
└── main.dart
Tujuan:
- app_routes.dart untuk konstanta path dan nama route
- app_router.dart untuk konfigurasi GoRouter
3. Buat Konstanta Route
File: lib/routes/app_routes.dart
class AppRoutes {
// Nama route (untuk goNamed/pushNamed)
static const String productsName = 'products';
static const String productDetailName = 'product-detail';
// Path route (hindari hardcode di banyak tempat)
static const String productsPath = '/products';
static const String productDetailPath = ':id'; // nested di bawah /products
}
Penjelasan singkat:
- Kita akan menempatkan detail sebagai nested route:
- List: /products
- Detail: /products/:id
4. Membuat Konfigurasi Router
File: lib/routes/app_router.dart
import 'package:go_router/go_router.dart';
import '../pages/home_page.dart';
import '../pages/detail_page.dart';
import 'app_routes.dart';
final GoRouter appRouter = GoRouter(
initialLocation: AppRoutes.productsPath,
routes: [
GoRoute(
name: AppRoutes.productsName,
path: AppRoutes.productsPath,
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
name: AppRoutes.productDetailName,
path: AppRoutes.productDetailPath, // ':id'
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(productId: id);
},
),
],
),
],
);
Catatan penting:
- Di sini, halaman detail menerima productId, bukan object produk.
- Nanti saat masuk API/Provider, detail akan mengambil data berdasarkan id.
5. Mengubah main.dart
File: lib/main.dart
Ganti MaterialApp(...) menjadi:
import 'package:flutter/material.dart';
import 'routes/app_router.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = ColorScheme.fromSeed(
seedColor: const Color(0xFF6D28D9),
brightness: Brightness.light,
);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'SMK Product App',
routerConfig: appRouter,
theme: ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
centerTitle: false,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
cardTheme: const CardTheme(
elevation: 0,
margin: EdgeInsets.zero,
),
),
);
}
}
6. Sesuaikan DetailPage Agar Menerima productId
Karena router sekarang mengirim productId, ubah halaman detail.
File: lib/pages/detail_page.dart
A. Ubah constructor
Sebelumnya:
- menerima Product product
Sekarang:
- menerima String productId
Contoh:
final String productId;
const DetailPage({
super.key,
required this.productId,
});
B. Ambil data produk berdasarkan id (masih dummy)
Agar data Home dan Detail konsisten, pindahkan dummy data ke satu tempat.
Buat:
lib/
└── data/
└── dummy_products.dart
File: lib/data/dummy_products.dart
import '../models/product.dart';
const List<Product> dummyProducts = [
Product(
id: "p1",
name: "Keyboard Mechanical Aurora",
category: "Aksesoris",
price: 349000,
imageUrl: "https://picsum.photos/seed/keyboard/800/600",
rating: 4.6,
description:
"Keyboard mechanical dengan switch empuk, backlight RGB, dan build quality kokoh. Cocok untuk coding dan gaming, nyaman dipakai lama.",
),
// Tambahkan produk lain seperti pertemuan 1
];
Di detail_page.dart, import dan cari produk:
import '../data/dummy_products.dart';
import '../models/product.dart';
final Product product = dummyProducts.firstWhere((p) => p.id == productId); // taruh kode ini diatas return scaffold
Setelah itu, lanjutkan UI detail seperti sebelumnya menggunakan product.
7. Update HomePage: Pindahkan Navigasi ke go_router
File: lib/pages/home_page.dart
A. Import
Tambahkan:
import 'package:go_router/go_router.dart';
import '../routes/app_routes.dart';
import '../data/dummy_products.dart';
B. Gunakan dummyProducts dari file data
Ganti sumber data dari fungsi _dummyProducts() menjadi:
final products = dummyProducts;
C. Navigasi ke detail (pushNamed + pathParameters)
Ganti Navigator.push(...) menjadi:
context.pushNamed(
AppRoutes.productDetailName,
pathParameters: {'id': item.id},
);
Checklist Pengujian
Pastikan:
1. Aplikasi masuk ke /products
2. Klik produk → detail terbuka
3. Tombol back → kembali ke list
4. Tidak ada Navigator.push untuk halaman utama
Tugas
Tugas Level Dasar
- Tambahkan route baru: /profile
- Buat halaman ProfilePage
- Navigasikan dari Home ke Profile menggunakan go_router
Tugas Level Industri
- Tambahkan halaman error (404) ketika route tidak ditemukan
- Pastikan tidak ada hardcode path string di halaman (gunakan konstanta route)
- Uji 3 skenario:
- buka
/products - buka
/products/p1 - buka
/products/route-salah→ harus masuk error page
Penutup
Hari ini kalian memindahkan aplikasi dari navigasi manual menjadi routing terpusat.
Ini pondasi penting sebelum masuk:
- state management (Provider)
- API
- Firebase