Bài 6. StatefulWidget và quản lý trạng thái cục bộ

Ứ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.

50 phútCơ bản
Bạn sẽ học được
  • 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)
Thuật ngữ cần biết
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àmKhi nào chạyDùng để
initStateMột lần khi tạoKhởi tạo controller, tải dữ liệu ban đầu
didUpdateWidgetWidget cha truyền cấu hình mớiSo sánh tham số cũ/mới
buildMỗi lần cần vẽ lạiTrả về giao diện
disposeKhi bị gỡ khỏi câyGiả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))
Mẹo hay

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.

Lưu ý

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.

Thực hành
  1. Làm bộ đếm có nút đặt lại và đổi màu số khi âm.
  2. Tạo công tắc (Switch) bật/tắt chế độ tối cho một màn hình.
  3. 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.
  4. In log ở initState và dispose để quan sát vòng đời.
Tóm tắt bài học
  • 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.