Flutter: State Management
Provider (simple, official recommendation for beginners)
// pubspec.yaml: provider: ^6.1.0
// ChangeNotifier model
class CartModel extends ChangeNotifier {
final List<Product> _items = [];
List<Product> get items => List.unmodifiable(_items);
int get count => _items.length;
void add(Product product) {
_items.add(product);
notifyListeners(); // rebuilds all listening widgets
}
void remove(Product product) {
_items.remove(product);
notifyListeners();
}
}
// Provide at top of tree
void main() => runApp(
ChangeNotifierProvider(
create: (_) => CartModel(),
child: const MyApp(),
),
);
// Multiple providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => AuthModel()),
],
child: const MyApp(),
)
// Consume
class CartIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
// watch — rebuilds when model changes
final count = context.watch<CartModel>().count;
// read — get value without subscribing (in callbacks)
// context.read<CartModel>().add(product);
return Badge(label: Text('$count'), child: const Icon(Icons.shopping_cart));
}
}
// Consumer — fine-grained rebuild scope
Consumer<CartModel>(
builder: (context, cart, child) => Text('Items: ${cart.count}'),
)Riverpod (modern, recommended)
// pubspec.yaml: flutter_riverpod: ^2.5.0, riverpod_annotation: ^2.3.0
// Provider definitions (global, not in widget tree)
final cartProvider = NotifierProvider<CartNotifier, List<Product>>(() => CartNotifier());
class CartNotifier extends Notifier<List<Product>> {
@override
List<Product> build() => [];
void add(Product p) => state = [...state, p];
void remove(Product p) => state = state.where((x) => x != p).toList();
}
// Async provider (auto-fetches and caches)
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
return ref.watch(apiServiceProvider).getUser(userId);
});
// Widget — extends ConsumerWidget (like StatelessWidget)
class CartScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(cartProvider);
return ListView(
children: items.map((p) => ProductTile(product: p)).toList(),
);
}
}
// Async provider with loading/error states
class UserProfile extends ConsumerWidget {
final String userId;
const UserProfile(this.userId);
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncUser = ref.watch(userProvider(userId));
return asyncUser.when(
loading: () => const CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
data: (user) => Text(user.name),
);
}
}Bloc / Cubit
// pubspec.yaml: flutter_bloc: ^8.1.0
// Cubit (simpler Bloc — no events, just methods)
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// Full Bloc (events + states — for complex flows)
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
final String email, password;
LoginRequested(this.email, this.password);
}
class LogoutRequested extends AuthEvent {}
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState { final User user; AuthAuthenticated(this.user); }
class AuthFailure extends AuthState { final String message; AuthFailure(this.message); }
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthService _authService;
AuthBloc(this._authService) : super(AuthInitial()) {
on<LoginRequested>((event, emit) async {
emit(AuthLoading());
try {
final user = await _authService.login(event.email, event.password);
emit(AuthAuthenticated(user));
} catch (e) {
emit(AuthFailure(e.toString()));
}
});
on<LogoutRequested>((event, emit) => emit(AuthInitial()));
}
}
// Provide and consume
BlocProvider(
create: (_) => CounterCubit(),
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text('$count'),
),
)Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free