Flutter State Management
•8 min read
This guide covers different state management solutions and best practices for Flutter applications.
1. Provider
1.1. Basic Provider
class CounterProvider extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } void decrement() { _count--; notifyListeners(); } } class CounterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => CounterProvider(), child: Consumer<CounterProvider>( builder: (context, provider, child) { return Column( children: [ Text('Count: ${provider.count}'), ElevatedButton( onPressed: () => provider.increment(), child: const Text('Increment'), ), ], ); }, ), ); } }
1.2. MultiProvider
class AppProviders extends StatelessWidget { final Widget child; const AppProviders({Key? key, required this.child}) : super(key: key); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AuthProvider()), ChangeNotifierProvider(create: (_) => ThemeProvider()), ChangeNotifierProvider(create: (_) => SettingsProvider()), ], child: child, ); } }
2. BLoC Pattern
2.1. Basic BLoC
class CounterEvent {} class IncrementEvent extends CounterEvent {} class DecrementEvent extends CounterEvent {} class CounterBloc extends Bloc<CounterEvent, int> { CounterBloc() : super(0) { on<IncrementEvent>((event, emit) => emit(state + 1)); on<DecrementEvent>((event, emit) => emit(state - 1)); } } class CounterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => CounterBloc(), child: BlocBuilder<CounterBloc, int>( builder: (context, count) { return Column( children: [ Text('Count: $count'), ElevatedButton( onPressed: () => context.read<CounterBloc>().add(IncrementEvent()), child: const Text('Increment'), ), ], ); }, ), ); } }
2.2. Complex BLoC
class UserEvent {} class LoadUserEvent extends UserEvent { final String userId; LoadUserEvent(this.userId); } class UserState {} class UserLoadingState extends UserState {} class UserLoadedState extends UserState { final User user; UserLoadedState(this.user); } class UserErrorState extends UserState { final String message; UserErrorState(this.message); } class UserBloc extends Bloc<UserEvent, UserState> { final UserRepository repository; UserBloc(this.repository) : super(UserLoadingState()) { on<LoadUserEvent>(_onLoadUser); } Future<void> _onLoadUser(LoadUserEvent event, Emitter<UserState> emit) async { try { emit(UserLoadingState()); final user = await repository.getUser(event.userId); emit(UserLoadedState(user)); } catch (e) { emit(UserErrorState(e.toString())); } } }
3. Riverpod
3.1. Basic Provider
final counterProvider = StateProvider<int>((ref) => 0); class CounterWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(counterProvider); return Column( children: [ Text('Count: $count'), ElevatedButton( onPressed: () => ref.read(counterProvider.notifier).state++, child: const Text('Increment'), ), ], ); } }
3.2. Async Provider
final userProvider = FutureProvider<User>((ref) async { final userId = ref.watch(userIdProvider); return await UserRepository().getUser(userId); }); class UserWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final userAsync = ref.watch(userProvider); return userAsync.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('Error: $error'), data: (user) => Text('User: ${user.name}'), ); } }
4. GetX
4.1. Reactive State
class CounterController extends GetxController { final count = 0.obs; void increment() => count.value++; void decrement() => count.value--; } class CounterWidget extends StatelessWidget { @override Widget build(BuildContext context) { final controller = Get.put(CounterController()); return Column( children: [ Obx(() => Text('Count: ${controller.count.value}')), ElevatedButton( onPressed: () => controller.increment(), child: const Text('Increment'), ), ], ); } }
4.2. Dependency Injection
class UserController extends GetxController { final UserRepository repository; final user = Rxn<User>(); final isLoading = false.obs; UserController(this.repository); Future<void> loadUser(String id) async { isLoading.value = true; try { user.value = await repository.getUser(id); } catch (e) { Get.snackbar('Error', e.toString()); } finally { isLoading.value = false; } } } class UserWidget extends StatelessWidget { @override Widget build(BuildContext context) { final controller = Get.find<UserController>(); return Obx(() => controller.isLoading.value ? const CircularProgressIndicator() : Text('User: ${controller.user.value?.name}')); } }
5. Redux
5.1. Basic Redux
class AppState { final int count; AppState({this.count = 0}); } class IncrementAction {} AppState reducer(AppState state, dynamic action) { if (action is IncrementAction) { return AppState(count: state.count + 1); } return state; } class CounterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return StoreProvider( store: Store<AppState>( reducer, initialState: AppState(), ), child: StoreConnector<AppState, int>( converter: (store) => store.state.count, builder: (context, count) { return Column( children: [ Text('Count: $count'), ElevatedButton( onPressed: () => StoreProvider.of<AppState>(context) .dispatch(IncrementAction()), child: const Text('Increment'), ), ], ); }, ), ); } }
6. Best Practices
-
State Management Selection
- Choose based on app complexity
- Consider team experience
- Evaluate performance needs
-
State Organization
- Keep state as local as possible
- Use immutable state
- Separate business logic
-
Performance
- Minimize rebuilds
- Use const constructors
- Implement proper disposal
-
Testing
- Test state changes
- Mock dependencies
- Verify UI updates
-
Maintenance
- Document state flow
- Follow consistent patterns
- Keep code organized
Conclusion
Remember these key points:
- Choose appropriate state management
- Keep state minimal and local
- Follow best practices
- Test thoroughly
- Document well
By following these practices, you can:
- Build scalable apps
- Improve maintainability
- Enhance performance
- Simplify testing
Keep improving your state management in Flutter applications!