Nguyễn Đình Cường
0 comment
09 Nov, 2026
Ứng dụng nào cũng có nhiều màn hình, và người dùng cần di chuyển qua lại giữa chúng một cách tự nhiên. Flutter cung cấp Navigator sẵn có, còn go_router là gói điều hướng khai báo được nhiều người ưa chuộng. Bài này giúp bạn hiểu cả hai và biết khi nào nên dùng.
Navigator quản lý các màn hình như một chồng (stack). Mở màn hình mới là đẩy (push) lên trên cùng, quay lại là lấy (pop) màn hình trên cùng ra. Cách dùng cơ bản:
// Mở màn hình chi tiết
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
);
// Quay lại
Navigator.pop(context);
Cách đơn giản nhất là truyền qua constructor của màn hình đích:
class DetailPage extends StatelessWidget {
final String productId;
const DetailPage({super.key, required this.productId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sản phẩm $productId')),
);
}
}
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage(productId: '42')),
);
Để nhận kết quả trả về, màn hình đích gọi Navigator.pop(context, giaTri) và nơi mở chờ bằng await Navigator.push(...).
Bạn có thể đặt tên cho route trong MaterialApp rồi gọi bằng Navigator.pushNamed. Cách này gọn với ứng dụng nhỏ, nhưng khi cần đường dẫn có tham số, kiểm tra đăng nhập hoặc liên kết sâu từ bên ngoài thì phần cấu hình trở nên rườm rà. Đó là lý do nhiều dự án chuyển sang go_router.
go_router cho phép bạn khai báo toàn bộ tuyến đường tập trung, dùng đường dẫn giống URL. Cài đặt bằng flutter pub add go_router.
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(productId: id);
},
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginPage(),
),
],
);
// Trong MaterialApp
MaterialApp.router(routerConfig: _router);
// Điều hướng
context.go('/product/42');
context.push('/login');
Khác biệt cần nhớ: go thay thế vị trí hiện tại theo đường dẫn mới, còn push thêm một màn hình lên ngăn xếp giống Navigator truyền thống.
Một tình huống rất phổ biến là chặn người chưa đăng nhập. Với go_router bạn dùng tham số redirect:
GoRouter(
redirect: (context, state) {
final loggedIn = authState.isLoggedIn;
final goingToLogin = state.matchedLocation == '/login';
if (!loggedIn && !goingToLogin) return '/login';
if (loggedIn && goingToLogin) return '/';
return null;
},
routes: [ /* ... */ ],
);
Trả về null nghĩa là cho phép đi tiếp đến đích ban đầu.
mounted trước khi điều hướng.Bắt đầu bằng Navigator để hiểu nguyên lý ngăn xếp, sau đó chuyển sang go_router khi dự án cần cấu trúc rõ hơn. Điều quan trọng là giữ cách điều hướng nhất quán trong toàn ứng dụng.
Ứng dụng thật thường có thanh tab ở dưới, mỗi tab giữ ngăn xếp màn hình riêng. Với go_router, dùng StatefulShellRoute để làm điều này mà vẫn giữ trạng thái từng tab khi chuyển qua lại:
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: NavigationBar(
selectedIndex: navigationShell.currentIndex,
onDestinationSelected: (i) => navigationShell.goBranch(i),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Trang chủ'),
NavigationDestination(icon: Icon(Icons.person), label: 'Cá nhân'),
],
),
);
},
branches: [
StatefulShellBranch(routes: [
GoRoute(path: '/', builder: (_, __) => const HomePage()),
]),
StatefulShellBranch(routes: [
GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()),
]),
],
)
Mỗi nhánh (branch) có ngăn xếp độc lập, nên người dùng đang xem sâu trong tab Trang chủ, chuyển sang tab Cá nhân rồi quay lại vẫn thấy đúng vị trí cũ. Cú pháp của gói có thể thay đổi giữa các phiên bản, vì vậy hãy đối chiếu với tài liệu của phiên bản bạn dùng.
Mặc định Flutter dùng hiệu ứng chuyển trang theo nền tảng: trượt sang ngang trên iOS, mờ và trượt lên trên Android. Bạn có thể tùy biến bằng PageRouteBuilder hoặc thuộc tính pageBuilder của go_router, nhưng hãy cân nhắc vì người dùng quen với hành vi hệ điều hành của họ. Hiệu ứng tùy biến chỉ nên dùng khi có lý do rõ ràng.
Về nút Back, Android có cử chỉ và nút quay lại của hệ thống. Nếu màn hình có dữ liệu nhập dở, hãy dùng PopScope để hỏi người dùng có muốn thoát không thay vì mất dữ liệu im lặng. Đồng thời kiểm tra luồng đăng nhập: sau khi đăng nhập xong nên thay thế màn hình đăng nhập chứ không đẩy thêm, để bấm Back không quay về form đăng nhập.
Muốn thực hành điều hướng trong một dự án đầy đủ chức năng, bạn có thể tìm khóa học phù hợp trên codebem.com.
Nguyễn Đình Cường
0 comment