Quick Guide to BLoC as State Manager For Your Next Flutter Project

Quick Guide to BLoC as State Manager For Your Next Flutter Project

Yo wassup flutter devs!

Today, we're diving deep into the world of Flutter and its state management.

Specifically, we'll be exploring the BLoC pattern. If you've been around the Flutter community for a while, you've probably heard of BLoC.

But if you're new or need a refresher, this guide is for you.


1. What is BLoC?

BLoC stands for Business Logic Component.

It's a design pattern that separates the presentation layer from the business logic in a Flutter application.

The main idea is to manage the state and make our apps more scalable and maintainable.


2. Why BLoC?

Flutter offers a variety of state management solutions, but BLoC stands out for a few reasons:

  • Separation of Concerns: BLoC ensures that the UI and business logic remain separate.

  • Reactivity: BLoC uses streams, making it inherently reactive.

  • Testability: Due to its architecture, testing becomes a breeze.

BLoC Image


3. Setting Up BLoC

Before diving into code, let's set up our environment:

  1. Create a new Flutter project with null safety:
flutter create --org com.example bloc_example
  1. Add the required packages to your pubspec.yaml:
dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^7.0.0
  1. Run flutter pub get to install the packages.

4. BLoC Basics

At its core, BLoC uses two main components: Events and States.

  • Events: These are the inputs to the BLoC. For example, a button press.

  • States: The output or the result after processing an event.


5. Creating Our First BLoC

Let's create a simple counter app using BLoC.

  1. Define Events:
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}
  1. Define States:
abstract class CounterState {
  final int value;
  CounterState(this.value);
}

class InitialState extends CounterState {
  InitialState() : super(0);
}

class UpdatedState extends CounterState {
  UpdatedState(int value) : super(value);
}

  1. Implement the BLoC:
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(InitialState());

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield UpdatedState(state.value + 1);
    } else if (event is DecrementEvent) {
      yield UpdatedState(state.value - 1);
    }
  }
}

  1. Integrate with the UI:
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter App')),
      body: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return Center(
            child: Text('Count: ${state.value}'),
          );
        },
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => bloc.add(IncrementEvent()),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => bloc.add(DecrementEvent()),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

6) Testing the BLoC

One of the advantages of BLoC is its testability. Here's a simple test for our CounterBloc:

void main() {
  group('CounterBloc', () {
    late CounterBloc bloc;

    setUp(() {
      bloc = CounterBloc();
    });

    test('initial state is 0', () {
      expect(bloc.state.value, 0);
    });

    blocTest<CounterBloc, CounterState>(
      'emits [1] when IncrementEvent is added',
      build: () => bloc,
      act: (bloc) => bloc.add(IncrementEvent()),
      expect: () => [UpdatedState(1)],
    );

    blocTest<CounterBloc, CounterState>(
      'emits [-1] when DecrementEvent is added',
      build: () => bloc,
      act: (bloc) => bloc.add(DecrementEvent()),
      expect: () => [UpdatedState(-1)],
    );
  });
}

7) Conclusion

The key to mastering BLoC (or any tool) is consistent practice and real-world application. So, start building, and happy coding!

I hope this guide provides a clear understanding of how to use BLoC in Flutter. If you have any questions or feedback, feel free to drop them in the comments below. Happy coding!

Also, This guide is a basic introduction to BLoC in Flutter. There are more advanced features and nuances that you can explore as you become more familiar with the pattern :)


Before We Go...

Hey, thanks for sticking around! If this post was your jam, imagine what’s coming up next.

I’m launching a YouTube channel, and trust me, you don't want to miss out. Give it a look and maybe even hit that subscribe button?

Tap to subscribe.

Until we meet again, code on and stay curious!

Got any doubt or wanna chat? React out to me on twitter or linkedin.