setState trong Flutter
1. setState là gì?
setState() thông báo cho Flutter rằng state đã thay đổi và cần rebuild widget:
setState(() {
_counter++; // Thay đổi state
});
// Flutter sẽ gọi lại build()2. Cách hoạt động
User Action → setState() → Framework schedules rebuild → build() called → UI updatedclass Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++; // 1. Thay đổi state
});
// 2. Flutter gọi build() lại
// 3. UI cập nhật với _count mới
}
@override
Widget build(BuildContext context) {
return Text('Count: $_count');
}
}3. Quy tắc sử dụng
✅ Đúng
// State thay đổi TRONG setState
setState(() {
_count++;
});
// Có thể thay đổi trước, setState để trigger rebuild
_count++;
setState(() {});
// Async work trước, setState sau
final data = await fetchData();
setState(() {
_items = data;
});❌ Sai
// Không gọi setState trong build()
@override
Widget build(BuildContext context) {
setState(() { ... }); // ❌ Error!
return Container();
}
// Không gọi setState sau dispose()
@override
void dispose() {
super.dispose();
}
void _loadData() async {
final data = await api.fetch();
setState(() { ... }); // ❌ Widget đã bị dispose!
}4. Xử lý Async với setState
class DataWidget extends StatefulWidget {
@override
State<DataWidget> createState() => _DataWidgetState();
}
class _DataWidgetState extends State<DataWidget> {
List<String> _items = [];
bool _isLoading = false;
Future<void> _loadData() async {
setState(() => _isLoading = true);
try {
final data = await api.fetchItems();
// Kiểm tra widget còn mounted không
if (mounted) {
setState(() {
_items = data;
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) return CircularProgressIndicator();
return ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => Text(_items[index]),
);
}
}5. Tối ưu setState
Chỉ rebuild những gì cần thiết
// ❌ setState ở widget lớn → rebuild toàn bộ
class BigWidget extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ExpensiveWidget1(),
ExpensiveWidget2(),
CounterDisplay(_count), // Chỉ cái này cần update
],
);
}
}
// ✅ Tách thành widget con
class CounterDisplay extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Text('$_count'); // Chỉ rebuild text
}
}Sử dụng const widgets
@override
Widget build(BuildContext context) {
return Column(
children: [
const Header(), // const = không rebuild
Text('Count: $_count'), // Chỉ cái này rebuild
const Footer(), // const = không rebuild
],
);
}6. Ví dụ thực tế
Form với validation
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
String _email = '';
String _password = '';
String? _errorMessage;
bool _isLoading = false;
void _submit() async {
if (_email.isEmpty || _password.isEmpty) {
setState(() => _errorMessage = 'Please fill all fields');
return;
}
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
await auth.login(_email, _password);
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_errorMessage = e.toString();
});
}
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (v) => _email = v,
decoration: InputDecoration(labelText: 'Email'),
),
TextField(
onChanged: (v) => _password = v,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
),
if (_errorMessage != null)
Text(_errorMessage!, style: TextStyle(color: Colors.red)),
ElevatedButton(
onPressed: _isLoading ? null : _submit,
child: _isLoading ? CircularProgressIndicator() : Text('Login'),
),
],
);
}
}Todo List
class TodoList extends StatefulWidget {
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
final List<Todo> _todos = [];
void _addTodo(String title) {
setState(() {
_todos.add(Todo(title: title));
});
}
void _toggleTodo(int index) {
setState(() {
_todos[index].isCompleted = !_todos[index].isCompleted;
});
}
void _deleteTodo(int index) {
setState(() {
_todos.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo = _todos[index];
return ListTile(
leading: Checkbox(
value: todo.isCompleted,
onChanged: (_) => _toggleTodo(index),
),
title: Text(todo.title),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteTodo(index),
),
);
},
);
}
}7. Khi nào KHÔNG dùng setState
- State chia sẻ giữa nhiều widgets → Dùng Provider, Riverpod, BLoC
- State phức tạp → Dùng state management library
- Global state → Dùng state management library
📝 Tóm tắt
| Quy tắc | Mô tả |
|---|---|
| Gọi trong State class | Không gọi từ bên ngoài |
| Không gọi trong build() | Gây infinite loop |
Kiểm tra mounted | Trước khi gọi async setState |
| Tối thiểu hóa scope | Chỉ rebuild những gì cần |
Pattern cơ bản:
setState(() {
_stateVariable = newValue;
});Last updated on