flutter
/

Cubit vs Bloc: Which One to Choose in Flutter?

Last Sync: Today

On this page

10
0%
Intermediate
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutterIntermediate

Cubit vs Bloc: Which One to Choose in Flutter?

When using flutter_bloc, you have two main ways to manage state: Cubit and Bloc. Both are based on the BLoC pattern, but they differ in complexity, boilerplate, and use cases. This guide will help you understand the differences, strengths, and when to choose one over the other.

What is Cubit?

Cubit is a simplified version of Bloc that uses methods to emit states. You define a state class and a cubit class that extends Cubit<State>. Inside the cubit, you write methods that emit new states by calling emit(). It’s perfect for simple state flows where you don’t need the power of event transforms.

DARTRead-only
1
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterState {
  final int value;
  CounterState(this.value);
}

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

  void increment() => emit(CounterState(state.value + 1));
  void decrement() => emit(CounterState(state.value - 1));
}

What is Bloc?

Bloc uses events and an on<Event> handler. You define events (as classes) and states, and the bloc listens for incoming events, processes them, and emits new states. Bloc gives you more control: you can transform events, handle debouncing, use emit.forEach, and manage complex asynchronous flows.

DARTRead-only
1
import 'package:flutter_bloc/flutter_bloc.dart';

// Events
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

// State
class CounterState {
  final int value;
  CounterState(this.value);
}

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<Increment>((event, emit) => emit(CounterState(state.value + 1)));
    on<Decrement>((event, emit) => emit(CounterState(state.value - 1)));
  }
}

Key Differences

FeatureCubitBloc
Event representationMethodsEvent classes
BoilerplateMinimalMore (events, handlers)
ComplexityLowModerate to high
Event transformersNot built-inBuilt-in (`on<Event>(..., transformer: ...)`)
Debouncing / throttlingManual (use `Future.delayed` or external)Built-in with `debounceTime`, `throttleTime`
Error handlingInside method try‑catchVia `onError` or event handler try‑catch
ReactivityImmediateCan be immediate or transformed
TestingSimpleSlightly more setup but still straightforward

When to Use Cubit

  • Simple UI state (e.g., toggles, counters, forms)
  • No need for event transformation or advanced debouncing
  • You prefer minimal boilerplate
  • Small to medium apps where complexity is low

When to Use Bloc

  • Complex state with many different actions
  • Need event transformation (e.g., debounce search input, throttle button clicks)
  • Want to use emit.forEach for reactive streams
  • You want to leverage event‑based debugging tools
  • Large applications with many developers (events provide clear contract)

Advanced Example: Debouncing with Bloc

One of Bloc’s strengths is built‑in event transformation. Here’s how to debounce a search query using Bloc.

DARTRead-only
1
// Event
class SearchQueryChanged extends SearchEvent {
  final String query;
  SearchQueryChanged(this.query);
}

// In bloc
on<SearchQueryChanged>(
  _onSearchQueryChanged,
  transformer: debounceTime(const Duration(milliseconds: 500)),
);

Future<void> _onSearchQueryChanged(SearchQueryChanged event, Emitter<SearchState> emit) async {
  // Perform search
  final results = await repository.search(event.query);
  emit(SearchLoaded(results));
}

// With Cubit, you would need to manage a timer yourself.

Migrating from Cubit to Bloc

If you start with Cubit and later need Bloc’s features, migration is straightforward:

  • Create event classes for each public method of your Cubit.
  • Replace the cubit class with a bloc that extends Bloc<Event, State>.
  • Move each method’s logic into an on<Event> handler.
  • Update UI to dispatch events instead of calling methods.
  • Update tests to use blocTest.

Best Practices

  • Start with Cubit – For most features, Cubit is sufficient and simpler.
  • Use Bloc when you need event transformation – If you find yourself implementing timers or manual debouncing, switch to Bloc.
  • Keep states immutable – Always create new state instances, don’t mutate.
  • Use Equatable for states and events – Reduces boilerplate and prevents unnecessary rebuilds.
  • Separate business logic – Even in Cubit, keep logic in the cubit, not in UI.

Common Mistakes

  • ❌ Using Bloc for everything – Over‑engineering adds unnecessary complexity. ✅ Use Cubit for simple cases.
  • ❌ Mutating state directly – Causes subtle bugs. ✅ Always emit a new state.
  • ❌ Calling emit after bloc is closed – Throws an error. ✅ Check isClosed before emitting if needed.
  • ❌ Not handling errors – Async exceptions can crash the app. ✅ Catch and emit error states.

Conclusion

Cubit and Bloc are both powerful, but they serve different levels of complexity. Cubit is the recommended starting point for most apps, offering simplicity and clear code. When you need advanced features like debouncing or event transforms, Bloc is there to support you. Choose the tool that matches your needs and keep your state management clean and testable.

Try it yourself

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(MyApp());

// ---- Cubit Example ----
class CounterState {
  final int value;
  CounterState(this.value);
}

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

  void increment() => emit(CounterState(state.value + 1));
  void decrement() => emit(CounterState(state.value - 1));
}

// ---- Bloc Example ----
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) => emit(CounterState(state.value + 1)));
    on<DecrementEvent>((event, emit) => emit(CounterState(state.value - 1)));
  }
}

// ---- App ----
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cubit vs Bloc Demo',
      home: MultiBlocProvider(
        providers: [
          BlocProvider(create: (_) => CounterCubit()),
          BlocProvider(create: (_) => CounterBloc()),
        ],
        child: DemoPage(),
      ),
    );
  }
}

class DemoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Cubit vs Bloc')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Cubit:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            BlocBuilder<CounterCubit, CounterState>(
              builder: (context, state) => Text('Count: ${state.value}', style: TextStyle(fontSize: 24)),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  icon: Icon(Icons.remove),
                  onPressed: () => context.read<CounterCubit>().decrement(),
                ),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () => context.read<CounterCubit>().increment(),
                ),
              ],
            ),
            SizedBox(height: 30),
            Text('Bloc:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            BlocBuilder<CounterBloc, CounterState>(
              builder: (context, state) => Text('Count: ${state.value}', style: TextStyle(fontSize: 24)),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  icon: Icon(Icons.remove),
                  onPressed: () => context.read<CounterBloc>().add(DecrementEvent()),
                ),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

How do you emit a new state in a Cubit?

A
By calling `state = newState`
B
By calling `emit(newState)`
C
By returning newState from a method
D
By using `setState`
Q2
of 3

Which feature is available in Bloc but not in Cubit?

A
State management
B
Event transformers (debounce, throttle)
C
UI rebuilding
D
Error handling
Q3
of 3

When should you prefer Bloc over Cubit?

A
For simple counters
B
When you need event debouncing or advanced transformation
C
Always, because it's more powerful
D
Never

Frequently Asked Questions

Can I use Cubit and Bloc together in the same app?

Yes, absolutely. You can use Cubit for simple features and Bloc for complex ones. flutter_bloc supports both seamlessly.

Is Bloc faster than Cubit?

No, performance is comparable. Bloc adds a thin layer of event processing, but the overhead is negligible. The choice is about developer experience, not speed.

How do I test a Cubit vs a Bloc?

Cubit can be tested by calling its methods and asserting the state after each call. Bloc can be tested with blocTest which allows you to seed events and expect states.

Should I use Bloc for API calls?

Yes, both work. With Cubit you call a method that calls the API and emits states. With Bloc you dispatch an event and handle it in the bloc. Bloc is particularly useful if you need to debounce or cancel requests.

What is the learning curve for Cubit vs Bloc?

Cubit has a very low learning curve – it’s like a simple class with methods. Bloc requires understanding events, handlers, and potentially transformers, so it’s a bit steeper.

Previous

bloc installation

Next

bloc why bloc

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.