Files
mljxxx 2995582a5e feat: add flutter-dev skill
Add comprehensive Flutter cross-platform development guide covering:
- Widget patterns and const optimization
- Riverpod/Bloc state management
- GoRouter navigation
- Performance optimization
- Testing strategies
- Platform-specific implementations

Made-with: Cursor
2026-03-26 17:35:55 +08:00

6.6 KiB

Bloc State Management

Bloc state management guide covering events, states, Cubit, and widget integration for complex business logic.

When to Use Bloc

Use Bloc/Cubit when you need:

  • Explicit event → state transitions
  • Complex business logic with multiple events
  • Predictable, testable state flows
  • Clear separation between UI and logic
Use Case Recommended
Simple mutable state Riverpod
Computed values Riverpod
Event-driven workflows Bloc
Forms, auth, wizards Bloc
Feature modules with complex logic Bloc

Core Concepts

Concept Description
Event User or system input that triggers state change
State Immutable representation of UI state
Bloc Maps events to new states
Cubit Simplified Bloc without events
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

Full Bloc Setup

Event Definition

sealed class CounterEvent {}

final class CounterIncremented extends CounterEvent {}
final class CounterDecremented extends CounterEvent {}
final class CounterReset extends CounterEvent {}

State Definition

class CounterState {
  final int value;
  final bool isLoading;

  const CounterState({
    required this.value,
    this.isLoading = false,
  });

  CounterState copyWith({int? value, bool? isLoading}) {
    return CounterState(
      value: value ?? this.value,
      isLoading: isLoading ?? this.isLoading,
    );
  }
}

Bloc Implementation

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(value: 0)) {
    on<CounterIncremented>(_onIncremented);
    on<CounterDecremented>(_onDecremented);
    on<CounterReset>(_onReset);
  }

  void _onIncremented(CounterIncremented event, Emitter<CounterState> emit) {
    emit(state.copyWith(value: state.value + 1));
  }

  void _onDecremented(CounterDecremented event, Emitter<CounterState> emit) {
    emit(state.copyWith(value: state.value - 1));
  }

  void _onReset(CounterReset event, Emitter<CounterState> emit) {
    emit(const CounterState(value: 0));
  }
}

Providing Bloc to Widget Tree

// Single bloc
BlocProvider(
  create: (_) => CounterBloc(),
  child: const CounterScreen(),
);

// Multiple blocs
MultiBlocProvider(
  providers: [
    BlocProvider(create: (_) => AuthBloc()),
    BlocProvider(create: (_) => ProfileBloc()),
    BlocProvider(create: (_) => SettingsBloc()),
  ],
  child: const AppRoot(),
);

Using Bloc in Widgets

BlocBuilder (UI Rebuilds)

class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CounterBloc, CounterState>(
      buildWhen: (prev, curr) => prev.value != curr.value,
      builder: (context, state) {
        return Text(
          state.value.toString(),
          style: Theme.of(context).textTheme.displayLarge,
        );
      },
    );
  }
}

BlocListener (Side Effects)

BlocListener<AuthBloc, AuthState>(
  listenWhen: (prev, curr) => prev.status != curr.status,
  listener: (context, state) {
    if (state.status == AuthStatus.failure) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.errorMessage ?? 'Error')),
      );
    }
    if (state.status == AuthStatus.authenticated) {
      context.go('/home');
    }
  },
  child: const LoginForm(),
);

BlocConsumer (Builder + Listener)

BlocConsumer<FormBloc, FormState>(
  listenWhen: (prev, curr) => prev.status != curr.status,
  listener: (context, state) {
    if (state.status == FormStatus.success) {
      context.pop();
    }
  },
  buildWhen: (prev, curr) => prev.isValid != curr.isValid,
  builder: (context, state) {
    return ElevatedButton(
      onPressed: state.isValid
          ? () => context.read<FormBloc>().add(FormSubmitted())
          : null,
      child: const Text('Submit'),
    );
  },
);

BlocSelector (Granular Rebuilds)

BlocSelector<UserBloc, UserState, String>(
  selector: (state) => state.user.name,
  builder: (context, name) {
    return Text('Hello, $name');
  },
);

Async Bloc Pattern

on<UserRequested>((event, emit) async {
  emit(state.copyWith(status: UserStatus.loading));

  try {
    final user = await repository.fetchUser(event.userId);
    emit(state.copyWith(status: UserStatus.success, user: user));
  } catch (e) {
    emit(state.copyWith(status: UserStatus.failure, error: e.toString()));
  }
});

Bloc + GoRouter Auth Guard

redirect: (context, state) {
  final authState = context.read<AuthBloc>().state;
  final isAuthRoute = state.matchedLocation.startsWith('/auth');

  if (authState.status != AuthStatus.authenticated && !isAuthRoute) {
    return '/auth/login';
  }
  if (authState.status == AuthStatus.authenticated && isAuthRoute) {
    return '/';
  }
  return null;
}

Testing Bloc

import 'package:bloc_test/bloc_test.dart';

blocTest<CounterBloc, CounterState>(
  'emits incremented value when CounterIncremented added',
  build: () => CounterBloc(),
  act: (bloc) => bloc.add(CounterIncremented()),
  expect: () => [const CounterState(value: 1)],
);

blocTest<CounterBloc, CounterState>(
  'emits multiple states',
  build: () => CounterBloc(),
  act: (bloc) {
    bloc.add(CounterIncremented());
    bloc.add(CounterIncremented());
    bloc.add(CounterDecremented());
  },
  expect: () => [
    const CounterState(value: 1),
    const CounterState(value: 2),
    const CounterState(value: 1),
  ],
);

Best Practices

Do Don't
Keep states immutable Mutate state directly
Use small, focused blocs Create "god blocs" with everything
One feature = one bloc Share blocs across unrelated features
Use Cubit for simple cases Overcomplicate with Bloc unnecessarily
Test all state transitions Skip bloc testing
Use buildWhen/listenWhen Rebuild on every state change

Widget Reference

Widget Purpose
BlocBuilder UI rebuilds based on state
BlocListener Side effects (navigation, snackbar)
BlocConsumer Both builder and listener
BlocSelector Granular state selection
BlocProvider Dependency injection
MultiBlocProvider Multiple bloc injection
RepositoryProvider Repository injection

Bloc is an open-source state management library by Felix Angelov.