Ứng dụng cần thay đổi theo tương tác người dùng. Bài này dạy StatefulWidget, setState, vòng đời State và cách truyền dữ liệu, sự kiện giữa widget cha và con.
- Phân biệt Stateless và Stateful
- Dùng setState đúng cách
- Nắm vòng đời: initState, dispose
- Nâng trạng thái lên widget cha (lifting state up)
- State
- Dữ liệu có thể đổi trong vòng đời widget.
- setState
- Báo Flutter rằng trạng thái đã đổi, cần build lại.
- initState / dispose
- Hàm chạy khi State được tạo / hủy.
- Callback
- Hàm cha truyền xuống để con báo sự kiện.
Khái niệm và lý thuyết
StatefulWidget gồm hai lớp: widget (bất biến, cấu hình) và State (tồn tại lâu, giữ dữ liệu). Khi gọi setState, Flutter đánh dấu widget cần dựng lại và gọi build ở khung hình kế tiếp.
Nguyên tắc: đặt trạng thái ở widget cha thấp nhất cần dùng nó. Dữ liệu đi xuống qua tham số constructor; sự kiện đi lên qua callback. Khi nhiều màn hình cần chung trạng thái, chuyển sang giải pháp quản lý trạng thái (Bài 10).
Bộ đếm
class BoDem extends StatefulWidget {
const BoDem({super.key});
@override
State<BoDem> createState() => _BoDemState();
}
class _BoDemState extends State<BoDem> {
int _dem = 0;
@override
Widget build(BuildContext context) => Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(onPressed: () => setState(() => _dem--), icon: const Icon(Icons.remove)),
Text('$_dem', style: const TextStyle(fontSize: 24)),
IconButton(onPressed: () => setState(() => _dem++), icon: const Icon(Icons.add)),
],
);
}Vòng đời State
| Hàm | Khi nào chạy | Dùng để |
|---|---|---|
| initState | Một lần khi tạo | Khởi tạo controller, tải dữ liệu ban đầu |
| didUpdateWidget | Widget cha truyền cấu hình mới | So sánh tham số cũ/mới |
| build | Mỗi lần cần vẽ lại | Trả về giao diện |
| dispose | Khi bị gỡ khỏi cây | Giải phóng controller, hủy lắng nghe stream |
Truyền dữ liệu cha – con
class NutYeuThich extends StatelessWidget {
const NutYeuThich({super.key, required this.daThich, required this.onDoi});
final bool daThich;
final ValueChanged<bool> onDoi;
@override
Widget build(BuildContext context) => IconButton(
icon: Icon(daThich ? Icons.favorite : Icons.favorite_border, color: Colors.red),
onPressed: () => onDoi(!daThich),
);
}
// Cha: NutYeuThich(daThich: _thich, onDoi: (v) => setState(() => _thich = v))Tách phần thay đổi thành widget nhỏ riêng để setState chỉ dựng lại phần nhỏ đó, giúp ứng dụng mượt hơn.
Gọi setState sau khi widget đã bị hủy (ví dụ sau await) gây lỗi. Kiểm tra if (!mounted) return; trước khi setState trong hàm async.
- Làm bộ đếm có nút đặt lại và đổi màu số khi âm.
- Tạo công tắc (Switch) bật/tắt chế độ tối cho một màn hình.
- Viết danh sách 5 bài hát, mỗi dòng có nút yêu thích; hiển thị tổng số đã thích ở AppBar.
- In log ở initState và dispose để quan sát vòng đời.
- StatefulWidget = widget bất biến + State lâu dài.
- setState kích hoạt build lại.
- initState/dispose để khởi tạo và dọn dẹp.
- Dữ liệu đi xuống, sự kiện đi lên qua callback.