Dialog xác nhận không nhất thiết phải tự thực hiện mọi hành động của màn hình. Nó có thể chỉ trả lời “đồng ý”, “hủy” hoặc không có kết quả, rồi để code gọi quyết định bước tiếp theo. Cách tách này giúp luồng xử lý dễ đọc và dễ kiểm tra hơn.
Bài dành cho dự án Flutter dùng null safety và Material widgets. API showDialog, BuildContext.mounted và Navigator.pop được đối chiếu ngày 28/10/2026. Ví dụ được rà theo tài liệu; lượt biên tập chưa build hoặc chạy widget test, nên các bước kiểm tra giao diện bên dưới là việc cần làm trên dự án thật.
1. Chọn kiểu kết quả trước khi viết nút
showDialog<bool> trả một Future có kết quả nullable. Trong ví dụ, nút Đồng ý trả true, nút Hủy trả false. Việc đóng mà không truyền một giá trị có thể cho null. Code gọi chỉ tiếp tục khi kết quả đúng bằng true.
Điều này làm rõ chính sách ngay tại nơi dùng dialog. Nếu sau này cần nhiều hơn hai lựa chọn, cân nhắc một enum hoặc kiểu kết quả riêng thay vì nhét các chuỗi không được kiểm soát vào cùng một luồng bool.
2. Một ví dụ hoàn chỉnh
Đặt vào lib/main.dart của ứng dụng Flutter. Ví dụ chỉ hiện thông báo xác nhận, không xóa dữ liệu và không gọi backend:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: DemoPage()));
class DemoPage extends StatelessWidget {
const DemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Xác nhận bản nháp')),
body: Center(
child: FilledButton(
onPressed: () async {
final approved = await showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (dialogContext) => AlertDialog(
title: const Text('Dùng bản nháp này?'),
content: const Text('Bạn có thể hủy để tiếp tục chỉnh sửa.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Hủy'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Đồng ý'),
),
],
),
);
if (!context.mounted || approved != true) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Đã xác nhận bản nháp.')),
);
},
child: const Text('Mở xác nhận'),
),
),
);
}
}
Dialog chỉ đóng route của nó với một giá trị. Sau khi Future hoàn tất, màn hình gọi nhận kết quả và quyết định có hiện SnackBar hay không. Không cần tạo biến global để chuyển lựa chọn giữa hai nơi.
3. Hai context có vai trò khác nhau
context truyền vào showDialog là context của nơi gọi. dialogContext thuộc cây widget được tạo cho dialog; ví dụ dùng nó để tìm Navigator khi đóng dialog. Đặt hai tên khác nhau giúp bạn nhận ra mình đang thao tác ở cây nào.
Trong ứng dụng có navigator lồng nhau, vị trí route và tùy chọn useRootNavigator cần được xem xét theo kiến trúc điều hướng. Ví dụ giữ mặc định của showDialog, không tự thêm rootNavigator ở mọi chỗ. Khi nút đóng làm mất cả màn hình, hãy kiểm tra context dùng cho pop và navigator đang chứa route.
Kết quả truyền cho pop cần phù hợp kiểu của route. Với dialog bool, hãy trả true hoặc false theo hợp đồng, không đổi sang 'yes' rồi kỳ vọng code gọi vẫn được kiểm tra kiểu như cũ.
4. Vì sao kiểm tra mounted sau await?
Trong lúc chờ một Future, màn hình gọi có thể không còn ở cây widget. Sau await, sử dụng context để tìm ScaffoldMessenger hoặc điều hướng tiếp cần kiểm tra nó vẫn hợp lệ.
context.mounted nói về vòng đời của context đó. Nó không cho biết request mạng đã thành công, dữ liệu còn mới hay người dùng vẫn có quyền thao tác. Nếu sau xác nhận còn có một bước gọi API, bạn cần kiểm tra luồng bất đồng bộ tiếp theo theo yêu cầu riêng.
Ở ví dụ, biểu thức approved != true gộp false và null thành “không tiếp tục”. Đó là lựa chọn của giao diện này, không phải khẳng định rằng hai kết quả luôn mang cùng ý nghĩa trong mọi sản phẩm.
Kiểm tra kết quả
Thử lần lượt Đồng ý, Hủy và chạm ra ngoài dialog. Chỉ Đồng ý được phép dẫn tới thông báo xác nhận. Nếu sản phẩm phân biệt hủy chủ động và đóng ngoài vùng, ghi lại các kết quả riêng thay vì chỉ kiểm tra đường thành công.
Trong một màn hình thử nghiệm có thể bị điều hướng khỏi cây khi dialog đang mở, kiểm tra rằng code sau await không sử dụng context đã unmount. Thử thêm cỡ chữ lớn để bảo đảm tiêu đề và hai hành động vẫn đọc, bấm được.
Giới hạn và bước tiếp theo
Ví dụ không thực hiện lưu bền vững, không chống lặp request và không chứng minh một thao tác backend đã xảy ra. Khi áp dụng cho xóa hoặc gửi dữ liệu thật, giữ việc xác nhận và việc thực thi thành các bước có trạng thái rõ ràng. Kết quả dialog là ý định của người dùng tại một thời điểm, không phải biên nhận hoàn tất nghiệp vụ.


