# State Management Patterns Comprehensive examples of state management approaches in Flutter. ## Pattern 1: ValueNotifier (Simplest) Best for: Simple local state with a single value ```dart class CounterScreen extends StatefulWidget { const CounterScreen({super.key}); @override State createState() => _CounterScreenState(); } class _CounterScreenState extends State { final ValueNotifier _counter = ValueNotifier(0); @override void dispose() { _counter.dispose(); super.dispose(); } void _increment() { _counter.value++; } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ValueListenableBuilder( valueListenable: _counter, builder: (context, value, child) { return Text('Count: $value'); }, ), ), floatingActionButton: FloatingActionButton( onPressed: _increment, child: const Icon(Icons.add), ), ); } } ``` ## Pattern 2: ChangeNotifier (Intermediate) Best for: Complex state or shared state across widgets ```dart // Model class CounterModel extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } void decrement() { _count--; notifyListeners(); } void reset() { _count = 0; notifyListeners(); } } // Usage class CounterScreen extends StatefulWidget { const CounterScreen({super.key}); @override State createState() => _CounterScreenState(); } class _CounterScreenState extends State { final CounterModel _model = CounterModel(); @override void dispose() { _model.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ListenableBuilder( listenable: _model, builder: (context, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Count: ${_model.count}'), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _model.decrement, child: const Icon(Icons.remove), ), const SizedBox(width: 16), ElevatedButton( onPressed: _model.increment, child: const Icon(Icons.add), ), ], ), ], ); }, ), ), ); } } ``` ## Pattern 3: Streams + StreamBuilder Best for: Sequences of asynchronous events ```dart class TimerModel { final _controller = StreamController(); Stream get stream => _controller.stream; int _count = 0; Timer? _timer; void start() { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _count++; _controller.add(_count); }); } void stop() { _timer?.cancel(); } void dispose() { _timer?.cancel(); _controller.close(); } } class TimerScreen extends StatefulWidget { const TimerScreen({super.key}); @override State createState() => _TimerScreenState(); } class _TimerScreenState extends State { final TimerModel _model = TimerModel(); @override void dispose() { _model.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: StreamBuilder( stream: _model.stream, initialData: 0, builder: (context, snapshot) { return Text('Time: ${snapshot.data}s'); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: _model.start, child: const Icon(Icons.play_arrow), ), const SizedBox(height: 8), FloatingActionButton( onPressed: _model.stop, child: const Icon(Icons.stop), ), ], ), ); } } ``` ## Pattern 4: MVVM (Advanced) Best for: Complex applications with business logic separation ```dart // Model class User { final String id; final String name; final String email; const User({ required this.id, required this.name, required this.email, }); } // ViewModel class UserViewModel extends ChangeNotifier { final UserRepository _repository; UserViewModel(this._repository); User? _user; bool _isLoading = false; String? _error; User? get user => _user; bool get isLoading => _isLoading; String? get error => _error; Future loadUser(String userId) async { _isLoading = true; _error = null; notifyListeners(); try { _user = await _repository.getUser(userId); } catch (e) { _error = e.toString(); } finally { _isLoading = false; notifyListeners(); } } Future updateUser(User user) async { _isLoading = true; notifyListeners(); try { await _repository.updateUser(user); _user = user; } catch (e) { _error = e.toString(); } finally { _isLoading = false; notifyListeners(); } } } // View class UserScreen extends StatefulWidget { final String userId; const UserScreen({super.key, required this.userId}); @override State createState() => _UserScreenState(); } class _UserScreenState extends State { late final UserViewModel _viewModel; @override void initState() { super.initState(); _viewModel = UserViewModel(UserRepository()); _viewModel.loadUser(widget.userId); } @override void dispose() { _viewModel.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('User Profile')), body: ListenableBuilder( listenable: _viewModel, builder: (context, child) { if (_viewModel.isLoading) { return const Center(child: CircularProgressIndicator()); } if (_viewModel.error != null) { return Center(child: Text('Error: ${_viewModel.error}')); } final user = _viewModel.user; if (user == null) { return const Center(child: Text('No user found')); } return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Name: ${user.name}'), const SizedBox(height: 8), Text('Email: ${user.email}'), ], ), ); }, ), ); } } ``` ## Pattern 5: Manual Dependency Injection Best for: Making dependencies explicit and testable ```dart // Service class AuthService { Future login(String email, String password) async { // Implementation return true; } bool isLoggedIn() { // Implementation return false; } } // Screen with dependency injection class LoginScreen extends StatefulWidget { final AuthService authService; const LoginScreen({ super.key, required this.authService, }); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { final _emailController = TextEditingController(); final _passwordController = TextEditingController(); bool _isLoading = false; @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } Future _handleLogin() async { setState(() => _isLoading = true); try { final success = await widget.authService.login( _emailController.text, _passwordController.text, ); if (success && mounted) { // Navigate to home } } finally { if (mounted) { setState(() => _isLoading = false); } } } @override Widget build(BuildContext context) { // Build UI return Scaffold( body: Center( child: _isLoading ? const CircularProgressIndicator() : ElevatedButton( onPressed: _handleLogin, child: const Text('Login'), ), ), ); } } // Usage class MyApp extends StatelessWidget { final AuthService authService = AuthService(); MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: LoginScreen(authService: authService), ); } } ``` ## State Management Decision Tree 1. **Single value, local to widget?** → Use `ValueNotifier` 2. **Multiple values or shared across widgets?** → Use `ChangeNotifier` 3. **Handling async event sequences?** → Use `Stream` + `StreamBuilder` 4. **Single async operation?** → Use `Future` + `FutureBuilder` 5. **Complex app with business logic separation?** → Use MVVM pattern 6. **Need explicit dependencies for testing?** → Use manual DI with constructor injection 7. **User explicitly requests Provider/Riverpod/Bloc?** → Use requested solution