As a product engineer, managing state effectively has always been crucial in building scalable and maintainable applications. In my journey with Flutter, I’ve found that separating business logic from the UI is key to enhancing the testability, scalability, and readability of the app. This is where the BLoC (Business Logic Component) pattern comes into play. In this post, I’ll demonstrate how I use BLoC in combination with the Singleton pattern for managing global state and MultiBlocProvider for providing all BLoCs at once at the start of the app. Additionally, I’ll show how to use BlocConsumer to handle UI updates and side effects separately.

Why I Choose BLoC for State Management

In any large-scale application, managing state consistently and in a structured way is crucial. BLoC allows me to decouple the business logic from the UI, making the code more modular and easier to test. It also uses Streams to handle state changes, which makes it highly suitable for complex state management scenarios.

I prefer using BLoC because:

  • It provides a clear separation of concerns by keeping business logic outside the UI.
  • It is based on streams and reactive programming, making it easy to handle asynchronous data.
  • It is highly scalable, as I can easily add new BLoCs without changing the architecture of the app.

Why Singleton Pattern with BLoC?

When I need a global state that should be shared across multiple screens or widgets, Singleton is the pattern I choose. It ensures that only one instance of a class exists throughout the entire app. For example, in a counter app, using a Singleton ensures that all parts of the app are accessing the same instance of CounterBloc, which avoids unnecessary resource consumption and state inconsistency.

Step 1: Add Dependencies

To use BLoC in Flutter, I start by adding the necessary dependencies in the pubspec.yaml file. Specifically, I use the flutter_bloc package, which simplifies BLoC implementation.

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0

Step 2: Define Events and States

The next step is to define the events and states. For this counter app, I’ll create two events (IncrementCounter and DecrementCounter) and a CounterState that holds the current counter value.

Counter Events:

abstract class CounterEvent {}

class IncrementCounter extends CounterEvent {}

class DecrementCounter extends CounterEvent {}

Counter States:

class CounterState {
  final int counterValue;
  const CounterState({required this.counterValue});
}

Step 3: Create the Singleton BLoC

In this step, I implement the CounterBloc using the Singleton pattern to ensure that only one instance of the CounterBloc is used throughout the app.

import ’package:flutterbloc/flutterbloc.dart’;

class CounterBloc extends Cubit<CounterState> {
  // Private static instance to enforce Singleton
  static final CounterBloc instance = CounterBloc.internal();

  // Private constructor
  CounterBloc._internal() : super(const CounterState(counterValue: 0));

  // Factory constructor to return the same instance
  factory CounterBloc() {
    return _instance;
  }

  // Logic to increment or decrement the counter
  void increment() => emit(CounterState(counterValue: state.counterValue + 1));
  void decrement() => emit(CounterState(counterValue: state.counterValue - 1));
}

In this implementation:

  • Singleton: The _instance is the only instance of the CounterBloc, ensuring that all parts of the app share the same CounterBloc.
  • increment and decrement: These methods are used to update the counter.

Step 4: Define Another BLoC (Optional)

I can also define additional BLoCs as needed. For example, a UserBloc can manage user-related information. Here’s how I define the UserBloc:

class UserBloc extends Cubit<String> {
  UserBloc() : super("No User");

  void setUser(String userName) => emit(userName);
}

Step 5: Use MultiBlocProvider for Initializing All Providers

To initialize multiple BLoCs at the start of the application, I use MultiBlocProvider. This allows me to provide multiple BLoC instances in one go. Here’s how I set up MultiBlocProvider in my main app widget:

import ’package:flutter/material.dart’;
import ’package:flutterbloc/flutterbloc.dart’;

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<CounterBloc>(create: (_) => CounterBloc()),  // Singleton Bloc
        BlocProvider<UserBloc>(create: (_) => UserBloc()),       // Another Bloc
      ],
      child: MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

Step 6: Using BlocConsumer for Handling State and Side Effects

Now, to handle both the UI updates and side effects separately, I use BlocConsumer. It lets me listen to state changes and perform side effects like showing a snack bar, logging, or navigating. Here’s how I implement BlocConsumer in the CounterScreen widget:

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(’Counter App with Singleton’)),
      body: Center(
        // Using BlocConsumer to listen to state and handle UI and side-effects
        child: BlocConsumer<CounterBloc, CounterState>(
          listener: (context, state) {
            // Handle side effects (e.g., show a snack bar, log something, etc.)
            if (state.counterValue == 10) {
              // Example of a side effect when counter reaches 10
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text(’Counter has reached 10!’)),
              );
            }
          },
          builder: (context, state) {
            // Build the UI based on the current state
            return Text(
              ’Counter: ${state.counterValue}’,
              style: TextStyle(fontSize: 50),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          // Increment button
          FloatingActionButton(
            onPressed: () {
              context.read<CounterBloc>().increment();  // Using Singleton instance
            },
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          // Decrement button
          FloatingActionButton(
            onPressed: () {
              context.read<CounterBloc>().decrement();  // Using Singleton instance
            },
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Conclusion

By combining BLoC, Singleton, and MultiBlocProvider, I can manage app state effectively and efficiently. The Singleton pattern ensures that only one instance of the CounterBloc exists across the app, while MultiBlocProvider simplifies the initialization of multiple BLoCs. Using BlocConsumer allows me to handle both the UI and side effects separately, keeping the app organized and responsive.

With these tools, I can ensure that my Flutter applications are scalable, maintainable, and responsive, which is essential when working on large projects. Whether it’s managing a simple counter or handling complex workflows, BLoC provides a clean and powerful solution for state management.

Our Trusted
Partner.

Unlock Valuable Cloud and Technology Credits

Imagine reducing your operational costs by up to $100,000 annually without compromising on the technology you rely on. Through our partnerships with leading cloud and technology providers like AWS (Amazon Web Services), Google Cloud Platform (GCP), Microsoft Azure, and Nvidia Inception, we can help you secure up to $25,000 in credits over two years (subject to approval).

These credits can cover essential server fees and offer additional perks, such as:

  • Google Workspace accounts
  • Microsoft accounts
  • Stripe processing fee waivers up to $25,000
  • And many other valuable benefits

Why Choose Our Partnership?

By leveraging these credits, you can significantly optimize your operational expenses. Whether you're a startup or a growing business, the savings from these partnerships ranging from $5,000 to $100,000 annually can make a huge difference in scaling your business efficiently.

The approval process requires company registration and meeting specific requirements, but we provide full support to guide you through every step. Start saving on your cloud infrastructure today and unlock the full potential of your business.

exclusive-partnersexclusive-partners

Let's TALK

Let's TALK and bring your ideas to life! Our experienced team is dedicated to helping your business grow and thrive. Reach out today for personalized support or request your free quote to kickstart your journey to success.

DIGITAL PRODUCTUI/UX DESIGNDIGITAL STUDIOBRANDING DESIGNUI/UX DESIGNEMAIL MARKETINGBRANDING DESIGNUI/UX DESIGNEMAIL MARKETING
DIGITAL PRODUCTUI/UX DESIGNDIGITAL STUDIOBRANDING DESIGNUI/UX DESIGNEMAIL MARKETINGBRANDING DESIGNUI/UX DESIGNEMAIL MARKETING