Bloc: Behind the Scene

Abdullah Ahmed Soomro
Blocship
Published in
5 min readApr 18, 2023

Hey there!

We are talking about these things:

Flutter Default Counter App

Stream

StreamController

Emit

StreamListner

Stream dispose

BlocBuilder

StreamBuilder

We are starting with the basics, let’s talk about the Flutter counter app which is the default Flutter application, we see it every time when we create a new project.

Flutter Default Counter App:

Flutter Default Counter Application

Code:

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Here they were using setState() to change the state:

void _incrementCounter() {
setState(() {
_counter++;
});
}

what happens if we don’t want to use the setState() or any state management package…

The answer is the stream.

Stream is a sequence of asynchronous events. It's like a pipe that allows you to receive values over time, instead of getting a single value at once. A stream is an object that emits events or data, which can be listened to by one or many subscribers.

Yes, the flutter bloc works on streams to change the states.

So let’s Start:

abstract class Cubit<T> {
T state;
Cubit(this.state);
}

This is an abstract class Cubit that holds a generic type T. The state the property holds the current state of the cubit, and the constructor sets the initial state.

StreamController():

final StreamController<T> _streamController = StreamController<T>();

This creates an StreamControllerobject that will be used to broadcast updates to the state.

emit():

This method sends a new state to the StreamController.

void emit(T state) {
_streamController.sink.add(state);
}

_streamController.sink is used to add new state values to the stream. When a new state value is added to the stream, the StreamBuilder widget in the UI layer is notified, and it rebuilds the UI to reflect the new state.

In this code, _streamController.sink.add(state) is called inside the emit() function of the Cubit class. The emit() function first updates the current state value with the new state value that is passed as an argument. Then, it adds the new state value to the stream using the _streamController.sink.add() method.

By doing so, the BLoC is able to notify the UI layer of any state changes, and update the UI accordingly.

Stream Listener:

Stream<T> get stream => _streamController.stream;

This getter returns the stream of states from the StreamController.

dispose():

It’s important to dispose of streams to avoid memory leaks and improve performance. When a widget is removed from the widget tree, it should dispose of any streams it’s listening to.

void close() {
_streamController.close();
}

This method is called when the Cubit is no longer needed and closes the StreamController.

The Abstract class:

abstract class Cubit<T> {
T state;
Cubit(this.state);

final StreamController<T> _streamController = StreamController<T>();

void emit(T state) {
_streamController.sink.add(state);
}

Stream<T> get stream => _streamController.stream;

void close() {
_streamController.close();
}
}

Counter Cubit:

class Counter extends Cubit<int> {
Counter() : super(0);

void increment() {
state++;
emit(state);
}
}

This is a concrete implementation of the Cubit class which holds an integer state. The increment() method is used to increment the current state and emit a new state.

BlocBuilder:

class BlocBuilder<B extends Cubit<S>, S> extends StatelessWidget {
const BlocBuilder({super.key, required this.builder, required this.bloc});

This is a generic widget that takes a Cubit and a builder function. It rebuilds the widget tree when the state changes.

final Widget Function(BuildContext context, S state) builder;
final B bloc;

These are the properties of the BlocBuilder. The builder function is called whenever the state changes, and the bloc is the Cubit that is being observed.

StreamBuilder:

return StreamBuilder<S>(
stream: bloc.stream,
builder: (context, snapshot) {
return builder(context, snapshot.data ?? bloc.state);
},
);

This is the build method of the BlocBuilder. It returns a StreamBuilder widget that rebuilds the widget tree whenever a new state is emitted by the Cubit. The builder function is called with the current BuildContext and the current state of the Cubit.

BlocBuilder Code:

class BlocBuilder<B extends Cubit<S>, S> extends StatelessWidget {
const BlocBuilder({super.key, required this.builder, required this.bloc});
final Widget Function(BuildContext context, S state) builder;
final B bloc;
@override
Widget build(BuildContext context) {
return StreamBuilder<S>(
stream: bloc.stream,
builder: (context, snapshot) {
return builder(context, snapshot.data ?? bloc.state);
},
);
}
}

MyApp():

class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bloc',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Bloc: Behind the Scene', bloc: Counter(),),
);
}
}

This is the main app widget that creates a MaterialApp widget. The title and theme properties set the title and theme of the app. The home property is set to MyHomePage, which is the main screen of the app.

MyHomePage():

class MyHomePage extends StatelessWidget {
const MyHomePage({
super.key,
required this.title,
required this.bloc,
});
final String title;
final Counter bloc;
void _incrementCounter() {
bloc.increment();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<Counter, int>(
bloc: bloc,
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$state',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}

This code defines a MyHomePage widget, which is a StatelessWidget.

The MyHomePage widget takes in three arguments: a Key, a String titled title, and a Counter object called bloc.

Inside the widget, there is a function _incrementCounter() that calls the increment() function of the bloc object when the user taps on the floating action button.

In the build() method, there is a BlocBuilder widget, which takes in two arguments: a Counter object called bloc and a builder function. The builder function returns a Scaffold widget with an AppBar, a Center widget containing a Column widget with a text widget showing the current state of the bloc, and a FloatingActionButton.

The BlocBuilder listens to the stream of the bloc object and rebuilds the widget tree whenever the state of the bloc changes. The current state of the bloc is passed as a parameter to the builder function.

Hope this will be useful for you guys!

--

--