Navigation trong Flutter – Chuyển trang và truyền dữ liệu


Mục tiêu:

  • Hiểu cách Flutter quản lý navigation theo cơ chế stack
  • Nắm được cách sử dụng Navigator.push và Navigator.pop
  • Biết cách truyền dữ liệu sang màn hình khác và nhận dữ liệu trả về
  • Làm quen với việc thiết kế ứng dụng nhiều màn hình

1. Navigation trong Flutter là gì

Trong hầu hết các ứng dụng, người dùng không chỉ làm việc trên một màn hình duy nhất. Navigation (điều hướng) cho phép ứng dụng di chuyển từ màn hình này sang màn hình khác. Flutter quản lý navigation bằng một ngăn xếp (stack):

  • Khi mở một trang mới, Flutter thực hiện push route đó vào stack.
  • Khi quay lại, Flutter thực hiện pop để lấy trang trên cùng ra khỏi stack.

Cơ chế này tương tự như thao tác với chồng sách: thêm một quyển mới đặt lên trên (push), hoặc bỏ quyển trên cùng ra ngoài (pop).


2. Navigator.push và Navigator.pop

Cách cơ bản nhất để điều hướng giữa các màn hình là dùng Navigator.push và Navigator.pop.

Ví dụ:

// Màn hình chính
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SecondScreen()),
    );
  },
  child: Text('Đi tới trang 2'),
);

// Màn hình thứ hai
ElevatedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('Quay lại'),
);

Trong ví dụ này:

  • Navigator.push đưa SecondScreen lên stack, ứng dụng hiển thị màn hình mới.
  • Navigator.pop xóa màn hình hiện tại và quay về màn hình trước đó.

3. Truyền dữ liệu giữa các màn hình

Khi di chuyển sang màn hình mới, chúng ta thường muốn truyền dữ liệu. Điều này thực hiện bằng cách truyền tham số vào constructor của màn hình mới.

// Màn hình 1
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailScreen(data: "Xin chào"),
  ),
);

// Màn hình 2
class DetailScreen extends StatelessWidget {
  final String data;

  DetailScreen({required this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Chi tiết")),
      body: Center(child: Text(data)),
    );
  }
}

Ở đây, màn hình 1 gửi chuỗi "Xin chào" sang DetailScreen. Màn hình 2 nhận giá trị này qua constructor và hiển thị.


4. Nhận dữ liệu trả về từ màn hình khác

Một tình huống phổ biến là mở một màn hình phụ để nhập dữ liệu, sau đó trả kết quả về màn hình trước. Flutter cho phép làm điều này nhờ Navigator.pop có tham số.

// Màn hình 1
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => InputScreen()),
);

print("Kết quả nhận được: $result");

// Màn hình 2
Navigator.pop(context, "Dữ liệu trả về");

Trong ví dụ:

  • Màn hình 1 gọi Navigator.push và chờ kết quả.
  • Màn hình 2 gọi Navigator.pop với giá trị "Dữ liệu trả về".
  • Màn hình 1 nhận kết quả này và sử dụng tiếp.

5. Ví dụ thực hành: Ứng dụng sản phẩm

Tạo ứng dụng có 2 màn hình:

  • Màn hình danh sách sản phẩm: hiển thị danh sách sản phẩm bằng ListView. Khi nhấn vào một sản phẩm, mở trang chi tiết.
  • Màn hình chi tiết sản phẩm: hiển thị thông tin chi tiết, có nút “Thêm vào giỏ hàng”. Khi bấm nút này, dùng Navigator.pop trả về trạng thái “đã thêm vào giỏ”.

Ở màn hình danh sách, sau khi nhận kết quả trả về, có thể hiển thị thông báo hoặc cập nhật giỏ hàng.


6. Navigation nâng cao

Với các ứng dụng nhỏ, Navigator.push và pop là đủ. Tuy nhiên, khi ứng dụng có nhiều màn hình hoặc yêu cầu phức tạp hơn, nên dùng các thư viện quản lý route:

  • go_router: thư viện chính thức, tích hợp tốt với Flutter.
  • auto_route: mạnh mẽ, hỗ trợ code generation để tự động tạo route.

Những thư viện này giúp code rõ ràng, dễ quản lý, và hỗ trợ deep linking, nested navigation.


7. Bài tập thực hành

  • Tạo ứng dụng có 3 màn hình: Trang chủ → Trang danh sách sản phẩm → Trang chi tiết sản phẩm.
  • Truyền dữ liệu sản phẩm từ danh sách sang chi tiết.
  • Khi bấm “Mua hàng”, trả về trạng thái để Trang chủ cập nhật tổng số sản phẩm đã mua.