Flutter State Management: BloC Pattern: Counter Tutorial
What is BloC Pattern and why use it?
Firstly, the pattern can provide a solid architecture for our apps and there is a disadvantage to using the Set state as it will re-build the whole widget and its widget tree. This impacts performance.
Before continuing to BloC pattern it is important that one is familiar with the following :
Streams
Streams can be understood as a flow of data, and it can be analogised as a pipe of water , with an input flow and output flow
Stream Controller
The Stream Controller can be thought of as what controls the stream of data, and also included in the analogy as the tap that controls the flow of water in the pipe.
Sink
The sink inserts new data inside the stream. The sink also has a property called stream , that is used to output the data.
Subscriptions
The subscriptions monitors the stream to see if there is new data coming in or out the stream.
In general terms, data will be flowing from the BLOC to the UI or from UI to the BLOC in the form of streams
In the following tutorial, we’re going to build a Counter in Flutter using the Bloc library.
Setting up the Project
Let’s make a Flutter project named flutter_counter. Then go ahead and import in your pubspec.yaml
the following packages:
flutter_bloc: ^2.0.0
meta: ^1.1.6
The Bloc
Our counter app is just going to have two buttons to increment/decrement the counter value and a Text
widget to display the current value. Let's get started designing the CounterEvents
.
enum CounterEvent { increment, decrement }
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
enum CounterEvent { increment, decrement }
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break;
}
}
}
The root: Main.dart
Now that we have our CounterBloc
fully implemented, we can get started creating our Flutter application.
We are using the BlocProvider
widget from flutter_bloc
in order to make the instance of CounterBloc
available to the entire subtree (CounterPage
). BlocProvider
also handles closing the CounterBloc
automatically so we don't need to use a StatefulWidget
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';import 'counter_bloc.dart';
import 'counterpage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider<CounterBloc>(
builder: (context) => CounterBloc(),
child: CounterPage(),
),
);
}
}
Counter Page
Finally, all that’s left is to build our Counter Page.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Center(
child: Text(
'$count',
style: TextStyle(fontSize: 24.0),
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counterBloc.add(CounterEvent.increment);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () {
counterBloc.add(CounterEvent.decrement);
},
),
),
],
),
);
}
}
We are able to access the CounterBloc
instance using BlocProvider.of<CounterBloc>(context)
because we wrapped our CounterPage
in a BlocProvider
. Furthermore, we are using the BlocBuilder
widget from flutter_bloc
in order to rebuild our UI in response to state changes (changes in the counter value).
That’s it! We’ve separated our presentation layer from our business logic layer. Our CounterPage
has no idea what happens when a user presses a button; it just adds an event to notify the CounterBloc
. Furthermore, our CounterBloc
has no idea what is happening with the state (counter value); it's simply converting the CounterEvents
into integers.
Thank you for reading! Feel free to make any other suggestions or recommendations for future challenges. Leave a few claps if you enjoyed it !