BLoC Pattern In Flutter

How to implement BLoC pattern in Flutter for State Management.

Introduction

 
This article explains how state is managed in Flutter. There are methods used you may already know about, such as Provider, Inherited Widget, Scoped Model, Redux, etc. BLoC pattern is also a state management technique. BLoC pattern is somehow advanced compared to Scoped Model and it’s better in performance as well.
 
BLoC pattern uses the Sink and Stream concepts in which Sink accepts an input as event and Stream provides the output. BLoC pattern is a bit complicated in implementation from scratch but there is also a plugin that makes it easy. However, we are going to implement it from scratch to understand it before we jump to use the readymade plugin. Now, let’s see BLoC pattern implementation in Flutter in detail.
 

Implementing BLoC Pattern in Flutter

 
Step 1
 
The first and basic step is to create a new application in Flutter. If you are a beginner, you can check my blog Create a first app in Flutter. For now, I have created an app named as “flutter_bloc_pattern”.
 
Step 2
 
Now, you can see that you have a counter app by default and here, our purpose is to make the same app using the BLoC pattern. We are adding a decrement operation to it as well.
 
Step 3
 
Now, create a new file named counter_bloc_provider.dart. In this file, we will add an abstract class for events for increment and decrement operation which will be mapped in bloc pattern to handle the stream and sink controls.
 
We have state and event controller implementation in which when the constructor is generated, the event will be allocated to the state according to the event type. Following is the programming implementation of that.
  1. import 'dart:async';  
  2.    
  3. abstract class CounterEvent {}  
  4.    
  5. class IncrementEvent extends CounterEvent {}  
  6.    
  7. class DecrementEvent extends CounterEvent {}  
  8.    
  9. class CounterBloc {  
  10.  int _counter = 0;  
  11.  final _counterController = StreamController<int>();  
  12.  StreamSink<int> get _incrementCounter => _counterController.sink;  
  13.  Stream<int> get counter => _counterController.stream;  
  14.  final _counterEventController = StreamController<CounterEvent>();  
  15.  Sink<CounterEvent> get counterEventSink => _counterEventController.sink;  
  16.  CounterBloc() {  
  17.    _counterEventController.stream.listen(_mapEventToState);  
  18.  }  
  19.  void _mapEventToState(CounterEvent event) {  
  20.    if (event is IncrementEvent)  
  21.      _counter++;  
  22.    else  
  23.      _counter--;  
  24.    _incrementCounter.add(_counter);  
  25.  }  
  26.    
  27.  void dispose(){  
  28.    _counterController.close();  
  29.    _counterEventController.close();  
  30.  }  
  31. }  
Step 4
 
Now, in the main.dart file, we will import bloc pattern class and define the stream for output and also, we will call an abstract event to add sink to increment and decrement counter variable. You will also see that the dispose() method is overridden because stream will consume memory which will be released when the widget is not in use. Following is the programming implementation of that.
  1. import 'package:flutter/material.dart';  
  2. import 'package:flutter_bloc_pattern/counter_bloc_provider.dart';  
  3.    
  4. void main() => runApp(MyApp());  
  5.    
  6. class MyApp extends StatelessWidget {  
  7.  @override  
  8.  Widget build(BuildContext context) {  
  9.    return MaterialApp(  
  10.      theme: ThemeData(  
  11.        primarySwatch: Colors.blue,  
  12.      ),  
  13.      home: MyHomePage(),  
  14.    );  
  15.  }  
  16. }  
  17.    
  18. class MyHomePage extends StatefulWidget {  
  19.  @override  
  20.  _MyHomePageState createState() => _MyHomePageState();  
  21. }  
  22.    
  23. class _MyHomePageState extends State<MyHomePage> {  
  24.  final _bloc = CounterBloc();  
  25.    
  26.  @override  
  27.  Widget build(BuildContext context) {  
  28.    return Scaffold(  
  29.      appBar: AppBar(  
  30.        title: Text('Flutter BLoC Patter'),  
  31.      ),  
  32.      body: Center(  
  33.        child: StreamBuilder(  
  34.          stream: _bloc.counter,  
  35.          initialData: 0,  
  36.          builder: (context, snapshot) {  
  37.            return Column(  
  38.              mainAxisAlignment: MainAxisAlignment.center,  
  39.              children: <Widget>[  
  40.                Text(  
  41.                  'Counter Value:',  
  42.                ),  
  43.                Text(  
  44.                  '${snapshot.data}',  
  45.                  style: Theme.of(context).textTheme.display1,  
  46.                ),  
  47.              ],  
  48.            );  
  49.          },  
  50.        ),  
  51.      ),  
  52.      floatingActionButton: Row(  
  53.        mainAxisAlignment: MainAxisAlignment.end,  
  54.        children: [  
  55.          FloatingActionButton(  
  56.            onPressed: () => _bloc.counterEventSink.add(DecrementEvent()),  
  57.            tooltip: 'Decrement',  
  58.            child: Icon(Icons.remove),  
  59.          ),  
  60.          SizedBox(width: 25,),  
  61.          FloatingActionButton(  
  62.            onPressed: () => _bloc.counterEventSink.add(IncrementEvent()),  
  63.            tooltip: 'Increment',  
  64.            child: Icon(Icons.add),  
  65.          ),  
  66.        ],  
  67.      ),  
  68.    );  
  69.  }  
  70.    
  71.  @override  
  72.  void dispose() {  
  73.    super.dispose();  
  74.    _bloc.dispose();  
  75.  }  
  76. }  
Hurray…. Run the app and test it on emulator/simulator or device :)))
 

Conclusion

 
State Management is one of the keys for performance improvement of a Flutter app and BLoC pattern is one of the best approaches to achieve that. It gives better performance than the Scoped Model. Though it is complex but very efficient in complex systems.