State là đối tượng quản lý dữ liệu trung tâm trong Flutter, được liên kết với StatefulWidget và chịu trách nhiệm lưu trữ thông tin có thể thay đổi và xây dựng giao diện. Theo tài liệu chính thức của Flutter (Flutter.dev, 2026), State tồn tại trong suốt vòng đời của widget và tồn tại sau khi widget được xây dựng lại, đảm bảo tính nhất quán dữ liệu giữa các lần cập nhật UI. Không giống như widget, State có thể sửa đổi các trường của nó và kích hoạt xây dựng lại thông qua lệnh gọi setState.
Những điểm chính
State là một đối tượng trong kiến trúc Flutter lưu trữ dữ liệu có thể thay đổi của StatefulWidget và xác định cách dữ liệu này được hiển thị trong giao diện. Mỗi StatefulWidget, khi được chèn vào cây, tạo ra chính xác một đối tượng State thông qua phương thức createState. State tồn tại độc lập với widget: nếu cha xây dựng lại StatefulWidget với các tham số mới, State vẫn giữ nguyên và nhận widget đã cập nhật thông qua thuộc tính widget.
Theo Tổng quan Kiến trúc Flutter (Google, 2026), việc tách Widget và State là một quyết định kiến trúc có chủ đích cho phép framework tái sử dụng các phần tử cây. Widget (một mô tả nhẹ) có thể được tạo và hủy nhiều lần, nhưng State (một đối tượng nặng với dữ liệu) vẫn ở trong bộ nhớ miễn là phần tử còn trong cây. Điều này ngăn mất dữ liệu trong quá trình xây dựng lại thường xuyên của các widget cha.
State triển khai giao diện StatefulWidget thông qua generics: class _MyState extends State<MyWidget>. Generic liên kết State với một loại StatefulWidget cụ thể, cung cấp quyền truy cập an toàn về kiểu đến các trường của nó thông qua thuộc tính widget.
Đối tượng State được lưu trữ trong StatefulElement — một lớp trung gian giữa Widget và RenderObject. StatefulElement tạo State thông qua createState, giữ một tham chiếu đến nó và chuyển State làm chủ sở hữu. Element chỉ bị hủy khi widget bị xóa khỏi cây — cho đến lúc đó, State tồn tại trong bộ nhớ.
Vòng đời của State là tất định và bao gồm một chuỗi các lệnh gọi nghiêm ngặt. Hiểu trình tự này là nền tảng để quản lý tài nguyên đúng cách và ngăn rò rỉ bộ nhớ.
initState được gọi đầu tiên khi State được tạo. Trong phương thức này, các bộ điều khiển, đăng ký luồng, bộ đếm thời gian và giá trị ban đầu của các trường được khởi tạo. Bắt buộc phải gọi super.initState() ở dòng đầu tiên. Ở giai đoạn initState, cây widget chưa được gắn kết hoàn toàn, vì vậy các phương thức như MediaQuery.of(context) có thể hoạt động không chính xác.
didChangeDependencies được gọi sau initState và mỗi khi các phụ thuộc InheritedWidget thay đổi. Đây là nơi, không phải trong initState, nên gọi MediaQuery.of(context) hoặc Theme.of(context), vì lúc này cây đã được gắn kết. Phương thức này cũng được gọi nếu widget di chuyển đến một ngữ cảnh khác nơi InheritedWidget cung cấp các giá trị khác.
build là phương thức chính của State trả về một cây widget. Nó được gọi sau initState, sau didChangeDependencies và sau mỗi setState. Phương thức build không được có tác dụng phụ — nó chỉ mô tả giao diện dựa trên các giá trị hiện tại của các trường State.
didUpdateWidget được gọi khi cha xây dựng lại StatefulWidget với các tham số mới. State có quyền truy cập vào widget cũ thông qua oldWidget và có thể so sánh nó với widget mới. Nếu tham số đã thay đổi, có thể cập nhật trạng thái, tải dữ liệu mới hoặc khởi động lại hoạt ảnh.
dispose là phương thức cuối cùng nơi tất cả tài nguyên được giải phóng: bộ điều khiển, đăng ký, bộ đếm thời gian. Sau dispose, State được đánh dấu là đã chết: mounted trả về false, gọi setState sẽ ném ra ngoại lệ. Bắt buộc phải gọi super.dispose() ở dòng cuối cùng của phương thức.
| Phương thức | Khi nào được gọi | Bắt buộc super |
|---|---|---|
| initState | Khi State được tạo | Có, ở dòng đầu tiên |
| didChangeDependencies | Sau initState và khi InheritedWidget thay đổi | Có |
| build | Sau initState, didChangeDependencies, setState | Không |
| didUpdateWidget | Khi nhận widget mới từ cha | Có |
| setState | Khi nhà phát triển gọi | Không |
| dispose | Khi xóa khỏi cây | Có, ở dòng cuối cùng |
Cơ chế hoạt động của State dựa trên ba nguyên tắc chính: liên kết với Element, phản ứng thông qua setState và truy cập cha thông qua thuộc tính widget. Khi Flutter xây dựng cây phần tử và gặp StatefulElement, nó gọi createState của widget được liên kết. State được tạo được lưu trữ trong phần tử và tồn tại cho đến khi phần tử bị xóa.
Khi setState được gọi, State tự đánh dấu là dirty và lên lịch xây dựng lại cho khung hình tiếp theo. Quan trọng: setState không gọi build ngay lập tức — nó chỉ đăng ký nhu cầu xây dựng lại. Flutter thu thập tất cả các phần tử dirty trong khung hình hiện tại và xây dựng lại chúng theo lô, giúp tối ưu hiệu suất. Sau khi build được gọi, State trở lại trạng thái sạch.
Thuộc tính widget cho phép State đọc các tham số được truyền cho hàm tạo StatefulWidget. Vì StatefulWidget là bất biến (giống StatelessWidget), các trường của nó không thay đổi — khi tham số thay đổi, cha tạo một widget mới và State nhận nó thông qua didUpdateWidget. Điều này đảm bảo State luôn làm việc với dữ liệu cha hiện tại.
Ví dụ cơ bản về State với một trường được sửa đổi bởi bộ đếm thời gian. Minh họa initState, setState và dispose:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
Ví dụ sử dụng thuộc tính widget để truy cập tham số cha và phản ứng với các thay đổi của chúng thông qua didUpdateWidget:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
Trong ví dụ thứ hai, State theo dõi các thay đổi của tham số đầu vào name và định dạng lại hiển thị chỉ khi có thay đổi thực sự. Nếu không có kiểm tra widget.name != oldWidget.name, phương thức sẽ được gọi trên mỗi lần cha xây dựng lại, ngay cả khi tên không thay đổi — công việc không cần thiết cho framework.
State và StatefulWidget là hai lớp khác nhau trong kiến trúc Flutter thực hiện các vai trò khác nhau. StatefulWidget là một lớp bọc bất biến nhẹ mô tả cấu hình widget và tạo State. State là một đối tượng nặng lưu trữ dữ liệu có thể thay đổi, quản lý đăng ký và xây dựng UI. Sự tách biệt này cho phép Flutter hủy và tạo widget mà không mất trạng thái.
Tất cả các trường của StatefulWidget phải là final và được đặt trong hàm tạo — chúng không thay đổi sau khi tạo. State, mặt khác, có thể sửa đổi các trường của nó bất kỳ lúc nào, nhưng tất cả các thay đổi phải được thực hiện trước lệnh gọi setState để Flutter biết về nhu cầu xây dựng lại. Đây là sự khác biệt chính: StatefulWidget là “những gì để hiển thị”, State là “làm thế nào để hiển thị và dữ liệu nào để sử dụng”.
Theo phân tích mã nguồn Flutter (Flutter SDK, 2026), StatefulWidget chỉ chứa một trường bắt buộc — createState, trong khi State có quyền truy cập vào BuildContext, có thể đăng ký luồng, quản lý hoạt ảnh và bộ điều khiển. Nên giữ StatefulWidget đơn giản nhất có thể, chuyển tất cả logic sang State.
Sự tách biệt Widget và State là một quyết định kiến trúc đảm bảo tính bất biến của cấu hình. Nếu StatefulWidget tự lưu trữ trạng thái, trạng thái sẽ bị mất trên mỗi lần cha xây dựng lại. Bằng cách chuyển trạng thái sang một đối tượng riêng biệt, Flutter đảm bảo dữ liệu tồn tại sau các lần xây dựng lại, trong khi các widget vẫn nhẹ và có thể so sánh được.
Đối tượng State bị cô lập — nó không có quyền truy cập trực tiếp vào State của các widget khác. Để trao đổi dữ liệu giữa các widget, sử dụng InheritedWidget hoặc các công cụ quản lý trạng thái bên ngoài: Provider, Riverpod, Bloc, Redux. Mỗi cách tiếp cận giải quyết vấn đề khác nhau: InheritedWidget hoạt động thông qua cây widget, Provider thông qua vùng chứa DI, Bloc thông qua luồng sự kiện.
Việc chọn công cụ phụ thuộc vào quy mô dự án. Đối với ứng dụng nhỏ, InheritedWidget và State cục bộ là đủ. Đối với dự án vừa và lớn, nên sử dụng Riverpod hoặc Bloc — chúng đảm bảo khả năng kiểm thử, khả năng dự đoán và tách logic khỏi UI. State sau đó chỉ được sử dụng cho dữ liệu cục bộ của widget (tiêu điểm, cuộn, hoạt ảnh).
Theo Khảo sát Cộng đồng Flutter 2025 (Flutter Foundation, tháng 12 năm 2025), Riverpod là giải pháp quản lý trạng thái phổ biến nhất trong các dự án mới (38%), tiếp theo là Bloc (31%) và Provider (22%). Cả ba công cụ đều tương thích với State và không yêu cầu từ bỏ vòng đời tiêu chuẩn.
Lỗi đầu tiên là quên kiểm tra mounted trước setState trong callback bất đồng bộ. Khi một widget bị xóa khỏi cây (ví dụ: người dùng đã rời khỏi màn hình), nhưng một hoạt động bất đồng bộ (yêu cầu HTTP) vẫn đang chạy, sau khi hoàn thành State đã chết. Gọi setState trong State đã chết sẽ ném ra ngoại lệ. Kiểm tra if (mounted) setState(...) giải quyết vấn đề.
Lỗi thứ hai là khởi tạo các phụ thuộc InheritedWidget trong initState thay vì didChangeDependencies. Trong initState, ngữ cảnh chưa được gắn kết, vì vậy MediaQuery.of(context) sẽ ném ra ngoại lệ. Tất cả các phụ thuộc InheritedWidget nên được thiết lập trong didChangeDependencies hoặc trong build.
Lỗi thứ ba là thay đổi các trường mà không gọi setState. Nếu nhà phát triển thay đổi trường State mà không có setState, Flutter sẽ không biết về thay đổi và UI sẽ không được cập nhật. Ví dụ: _list.add(item) mà không có setState((){}) sau đó sẽ sửa đổi danh sách, nhưng màn hình sẽ vẫn như cũ.
Một mẫu an toàn cho các hoạt động bất đồng bộ trong State:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
Kiểm tra mounted đảm bảo setState chỉ được gọi trên State còn sống, ngăn ngoại lệ “setState called after dispose”.
Câu hỏi thường gặp
StatefulWidget là một cấu hình widget bất biến, trong khi State là một đối tượng có thể thay đổi lưu trữ dữ liệu và quản lý vòng đời. Widget có thể được tạo lại, State thì không. StatefulWidget tạo State thông qua createState.
Chính xác là một. Phương thức createState được gọi một lần khi StatefulWidget lần đầu tiên được chèn vào cây. Ngay cả khi cha xây dựng lại nhiều lần, đối tượng State vẫn giữ nguyên cho đến khi loại hoặc Key của widget thay đổi.
mounted là một cờ boolean cho biết State có trong cây widget hay không. Sau khi gọi dispose, mounted trở thành false. Nó được sử dụng để kiểm tra trước setState trong các callback bất đồng bộ để tránh ngoại lệ.
Không. State luôn được gắn với một StatefulWidget cụ thể thông qua generics: State<T extends StatefulWidget>. Tạo State trực tiếp, không có liên kết với widget, là không thể về mặt kiến trúc.
Một ngoại lệ được ném ra: “setState called after dispose”. Sau dispose, State được coi là đã chết và mọi nỗ lực xây dựng lại UI thông qua setState đều bị cấm. Giải pháp là kiểm tra mounted trước mỗi setState.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm