🔄 setState
1. setState là gì?
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “3. Quy tắc sử dụng”✅ Đúng
Phần tiêu đề “✅ Đúng”// State thay đổi TRONG setStatesetState(() { _count++;});
// Có thể thay đổi trước, setState để trigger rebuild_count++;setState(() {});
// Async work trước, setState saufinal data = await fetchData();setState(() { _items = data;});❌ Sai
Phần tiêu đề “❌ Sai”// Không gọi setState trong build()@overrideWidget build(BuildContext context) { setState(() { ... }); // ❌ Error! return Container();}
// Không gọi setState sau dispose()@overridevoid dispose() { super.dispose();}
void _loadData() async { final data = await api.fetch(); setState(() { ... }); // ❌ Widget đã bị dispose!}4. Xử lý Async với setState
Phần tiêu đề “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
Phần tiêu đề “5. Tối ưu setState”Chỉ rebuild những gì cần thiết
Phần tiêu đề “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 conclass CounterDisplay extends StatefulWidget { @override Widget build(BuildContext context) { return Text('$_count'); // Chỉ rebuild text }}Sử dụng const widgets
Phần tiêu đề “Sử dụng const widgets”@overrideWidget 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ế
Phần tiêu đề “6. Ví dụ thực tế”Form với validation
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “📝 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;});