image

07 Dec 2025

9K

35K

Flutter State Management with Cubit for Beginners

Managing the state of your application is a crucial aspect of building robust and scalable Flutter apps. As your application grows, handling data flow and UI updates can become complex without a proper strategy. This article introduces Cubit, a simpler variant of the Bloc pattern, as an excellent starting point for beginners to understand and implement state management in Flutter.

What is State Management and Why Cubit?

In Flutter, "state" refers to any data that can change during the lifetime of the application and affects the UI. For instance, the text displayed on a screen, the items in a list, or the authentication status of a user are all part of an app's state.

Without a structured approach to state management, updating the UI in response to data changes can lead to:

  • Unpredictable behavior and bugs.
  • Difficulty in tracking data flow.
  • Poor code maintainability and readability.

Cubit, part of the flutter_bloc ecosystem, offers a straightforward way to separate business logic from the UI. It's built on the same principles as Bloc but with less boilerplate, making it easier to grasp for those new to reactive state management.

Prerequisites

Before diving into Cubit, it's assumed you have a basic understanding of:

  • Flutter fundamentals (widgets, `StatefulWidget` vs `StatelessWidget`).
  • Dart programming language basics.

Core Concepts of Cubit

Cubit simplifies the concept of managing state by focusing on a few key components:

1. State

The "State" is the actual data that your UI displays. In Cubit, states are usually represented by immutable Dart classes. When the state changes, the UI updates.

2. Cubit

A "Cubit" is a class that stores the current state and exposes methods to update it. Unlike a traditional Bloc which uses events, a Cubit directly exposes methods that can be called to change its state. It emits new states to its listeners.

3. BlocProvider

BlocProvider is a Flutter widget that provides a Cubit (or Bloc) to its children down the widget tree. This allows you to access your Cubit from any widget below the BlocProvider in a clean and efficient manner.

4. BlocBuilder

BlocBuilder is a Flutter widget that rebuilds a part of your UI in response to new states emitted by a Cubit. It takes a builder function that will be called whenever the Cubit's state changes, allowing you to reactively update your UI.

Step-by-Step Example: A Simple Counter App

Let's build a basic counter application to demonstrate Cubit in action.

1. Project Setup

First, create a new Flutter project:


flutter create cubit_counter_app
cd cubit_counter_app

2. Add Dependencies

Open your pubspec.yaml file and add the flutter_bloc and equatable packages:


dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.3 # Use the latest version
  equatable: ^2.0.5 # For easier state comparison

Then run flutter pub get to fetch the packages.

3. Define the State

Create a new file, e.g., lib/counter_state.dart, to define our counter state. We'll use Equatable to easily compare state instances.


import 'package:equatable/equatable.dart';

class CounterState extends Equatable {
  final int counterValue;

  const CounterState({required this.counterValue});

  @override
  List<Object> get props => [counterValue];
}

Our CounterState simply holds an integer counterValue.

4. Create the Cubit

Next, create lib/counter_cubit.dart to define our CounterCubit:


import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:cubit_counter_app/counter_state.dart';

class CounterCubit extends Cubit<CounterState> {
  // Initialize the cubit with an initial state
  CounterCubit() : super(const CounterState(counterValue: 0));

  // Method to increment the counter
  void increment() {
    emit(CounterState(counterValue: state.counterValue + 1));
  }

  // Method to decrement the counter
  void decrement() {
    emit(CounterState(counterValue: state.counterValue - 1));
  }
}

In this Cubit:

  • It extends Cubit<CounterState>, indicating it manages states of type CounterState.
  • The constructor calls super() to set the initial state (counterValue: 0).
  • increment() and decrement() methods are defined. Inside these methods, emit() is called with a new CounterState instance to update the state.

5. Integrate with UI

Now, let's connect our Cubit to the Flutter UI. We'll modify lib/main.dart and create a separate HomePage widget.

First, modify lib/main.dart:


import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:cubit_counter_app/counter_cubit.dart';
import 'package:cubit_counter_app/home_page.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterCubit(),
      child: MaterialApp(
        title: 'Cubit Counter App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(),
      ),
    );
  }
}

Here, we wrap our MaterialApp with a BlocProvider<CounterCubit>. The create callback provides an instance of CounterCubit, making it available to all widgets below it in the tree.

Next, create lib/home_page.dart:


import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:cubit_counter_app/counter_cubit.dart';
import 'package:cubit_counter_app/counter_state.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cubit Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            // BlocBuilder listens to state changes and rebuilds its child
            BlocBuilder<CounterCubit, CounterState>(
              builder: (context, state) {
                return Text(
                  '${state.counterValue}',
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              // Access the Cubit and call its increment method
              context.read<CounterCubit>().increment();
            },
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () {
              // Access the Cubit and call its decrement method
              context.read<CounterCubit>().decrement();
            },
            tooltip: 'Decrement',
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

In HomePage:

  • We use BlocBuilder<CounterCubit, CounterState> to listen for changes in CounterCubit's state. Whenever a new CounterState is emitted, the builder function runs, updating the Text widget with the new counterValue.
  • For the FloatingActionButtons, we use context.read<CounterCubit>() to get an instance of our Cubit and then call its increment() or decrement() methods directly. This triggers the Cubit to emit a new state.

Run the Application

You can now run your Flutter application:


flutter run

You should see a counter app where tapping the '+' or '-' buttons updates the counter value on the screen, demonstrating Cubit-based state management.

Why Cubit is Great for Beginners

  • Simplicity: Cubit avoids the complexity of events and mapping events to states, which can be overwhelming for newcomers. You directly call methods on the Cubit to update the state.
  • Less Boilerplate: Compared to Bloc, Cubit requires less code for setup, making it quicker to get started and understand the core concepts.
  • Direct Method Calls: The direct method invocation (`cubit.increment()`) feels more intuitive and similar to traditional programming paradigms.
  • Foundation for Bloc: Learning Cubit provides a solid foundation for understanding Bloc, as Cubit is essentially a Bloc without events. Once comfortable with Cubit, transitioning to Bloc for more complex scenarios becomes easier.

Conclusion

Cubit offers an elegant and simple solution for state management in Flutter, especially for beginners. By separating business logic from UI, it promotes cleaner, more maintainable, and testable code. Through the simple counter example, you've learned how to define states, create Cubits, and integrate them with your Flutter UI using BlocProvider and BlocBuilder. As you grow more confident, you can explore other features of the flutter_bloc package, such as BlocListener for side effects and the full Bloc pattern for more intricate event-driven logic.

Related Articles

Dec 19, 2025

Building a Widget List with Sticky

Building a Widget List with Sticky Header in Flutter Creating dynamic and engaging user interfaces is crucial for modern applications. One common UI pattern th

Dec 19, 2025

Mastering Transform Scale & Rotate Animations in Flutter

Mastering Transform Scale & Rotate Animations in Flutter Flutter's powerful animation framework allows developers to create visually stunning and highly intera

Dec 19, 2025

Building a Countdown Timer Widget in Flutter

Building a Countdown Timer Widget in Flutter Countdown timers are a fundamental component in many modern applications, ranging from e-commerce platforms indica