Flutter
03 / 04

State Management: Provider, Riverpod & Bloc

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