online software programming courses

Flutter Bloc Pattern Explained Step by Step

Bloc is used for state management is flutter. It takes away some of the complex operation from the coding and makes life simple for programmers. 

The main purpose of the Bloc is seperating business logic from view. To further simplied you can understand it like this, it converts events to states. And events come from user interaction

User Interaction----> Events---->States

A BLoC takes a stream of events as input and transforms them into a stream of states as output.

Bloc or Bloc pattern for beginners is hard to understand at beginning unlike Getx. So here I will explain it pretty straghtforward and will use a simple counter app example to demonstrate how to use it.

Create a project and Install it with the below command

flutter pub add flutter_bloc

Here is a list of ideas for the classes you need to know 

1. BlocProvider

Using BlocProvider you will create a BLoc or Cubit. It takes a create a function. Within this create function, you will call the class that extends Bloc. BlocProvider is usually called from your view. So it's also the entry point before you use Bloc. It creates the actual Bloc.

BlocProvider is called from your view.

2. BlocBulider

BlocBuilder builds a widget when state is updated. You should wrap widget using BlocBuilder which update the widget based on state.

Block Builder is called from your view

3. mapEventToState

If you use Bloc, everything is translated as event from user side. User interaction in the view is translated as event to Bloc. Bloc receives this events and map them to states. This is where mapEventToState comes to use. So from the name you can easily understand, it converts event to state.

mapEventToState should be implemented within the class which extends Bloc. 

4.BlocProvider.of<T>

This method allows widgets or your ui to access the Bloc instances. You will use Bloc instances to show on the ui or make changes to the values or properties or fields of Bloc. 

So this is used for dependency injection. Dependency injection means your app depends on this. 

So with this you can access the instances of Bloc. 

main.dart

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: BlBlocCounterPage(),
    );
  }
}

 

Creating the bloc ui

BlBlocCounterPage.dart


class BlBlocCounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /*
    BlocProvider manage's bloc lifecycle,
     */
    return BlocProvider(
      create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
      child: Builder(builder: (context) => _buildPage(context)),
    );
  }

  Widget _buildPage(BuildContext context) {
   // Section one
    final bloc = BlocProvider.of<BlBlocCounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Bloc-Bloc Example')),
      body: Center(
        //Section two
        child: BlocBuilder<BlBlocCounterBloc, BlBlocCounterState>(
          builder: (context, state) {
            return Text(
              'Pressed ${bloc.state.count} times',
              style: TextStyle(fontSize: 30.0),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => bloc.add(CounterIncrementEvent()),
        child: Icon(Icons.add),
      ),
    );
  }
}

Explain

From the above code you can see that, you can call BlocProvider, BlocBuilder and .of<T> from the same file. BlocProvider does the initialization of Bloc. The type T takes the class that extends Bloc. 

In this example, BlBlocCounterBloc extends Bloc. BlocProvider takes create and child. create takes functions for initializing the Bloc and child is the one which inject the dependency. So in child you will call BlocProvider.of<BIBlocCounterBloc>(context) to use the dependency. This process is also called provider.

 

Creating Bloc 

You can see from section one (in the code), that we created an instance of the Bloc.

class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
  BlBlocCounterBloc() : super(BlBlocCounterState().init());

  @override
  Stream<BlBlocCounterState> mapEventToState(BlBlocCounterEvent event) async* {
    if (event is InitEvent) {
      yield await init();
    } else if (event is CounterIncrementEvent) {
      yield increment();
    }
  }

  Future<BlBlocCounterState> init() async {
    return state.clone();
  }

  ///自增
  BlBlocCounterState increment() {
    return state.clone()..count = ++state.count;
  }
}

Our class extends Bloc and uses events and states.

Creating Events 

Events.dart

abstract class BlBlocCounterEvent {}

class InitEvent extends BlBlocCounterEvent {}

class CounterIncrementEvent extends BlBlocCounterEvent {}

 

Creating States

BlBlocCounterState.dart

class BlBlocCounterState {
  late int count;

  BlBlocCounterState init() {
    return BlBlocCounterState()..count = 0;
  }

  BlBlocCounterState clone() {
    return BlBlocCounterState()..count = count;
  }
}