BLoC Pattern in Flutter explained with Real Example
By: Vipin Vijayan
Tags: Mobile, Flutter, Mobile Apps, Mobile App Development, Flutter App Development

In this article we will learn BLoC pattern in flutter for State Management with a simple real world example. You can find the second part of this tutorial here.

BLOC Flutter State Management

Watch Video Tutorials

Subscribe to my channel for more videos.

For this example, we will try to consume a web service. The service we are going to use is

https://jsonplaceholder.typicode.com/albums

What BLoC does?

BLoC helps to separate you presentation and business logic.

So in simple terms, we will write all our business logic inside the bloc file.

So what basically Bloc does is, it will take an event and a state and return a new state which we will use to update the UI.

You don’t need to maintain any local state in your application if you have BLoC.

Let’s start…

We will first write a service class that will consume the web service.

Add Dependencies

Open your pubspec.yaml file add the dependencies

Add all the dependencies needed for this example

dependencies:

flutter:

sdk: flutter cupertino_icons: ^0.1.2

flutter_bloc: ^6.0.4

equatable: ^1.2.4

http: ^0.12.2

http package to get data from the web service.

flutter_bloc for using the BLoC pattern.

equatable for comparing objects.

Let’s create the model classes for the service response first.

Model Class

Below is the response class for the service response

import 'dart:convert'; List<Album> albumFromJson(String str) =>

List<Album>.from(json.decode(str).map((x) => Album.fromJson(x))); String albumToJson(List<Album> data) =>

json.encode(List<dynamic>.from(data.map((x) => x.toJson()))); class Album {

Album({

this.userId,

this.id,

this.title,

});int userId; int id;

String title; factory Album.fromJson(Map<String, dynamic> json) => Album(

userId: json["userId"],

id: json["id"],

title: json["title"],

);Map<String, dynamic> toJson() => {

"userId": userId,

"id": id,

"title": title,

};

}

Once we have the model classes, we are ready for the service consumption.

Let’s create the services file and get the data.

Create a new file named “services.dart” and copy the below.



import 'package:http/http.dart' as http;

import 'package:http/http.dart';abstract class AlbumsRepo {

Future<List<Album>> getAlbumList();

}class AlbumServices implements AlbumsRepo {

//

static const _baseUrl = 'jsonplaceholder.typicode.com';

static const String _GET_ALBUMS = '/albums';

Future<List<Album>> getAlbumList() async {

Uri uri = Uri.https(_baseUrl, _GET_ALBUMS);

Response response = await http.get(uri);

List<Album> albums = albumFromJson(response.body);

return albums;

}

} import 'package:flutter_demos/model/albums_list.dart';import 'package:http/http.dart' as http;import 'package:http/http.dart';abstract class AlbumsRepo {Future > getAlbumList();}class AlbumServices implements AlbumsRepo {//static const _baseUrl = 'jsonplaceholder.typicode.com';static const String _GET_ALBUMS = '/albums'; @override Future > getAlbumList() async {Uri uri = Uri.https(_baseUrl, _GET_ALBUMS);Response response = await http.get(uri);List albums = albumFromJson(response.body);return albums;

Now let’s use BLOC to get the data to the UI.

Events

Here we have only one event — fetchAlbums.

Let’s create a class named events.dart and add the below enum.

enum AlbumEvents {

fetchAlbums,

}

States

Below are the states when we start getting the albums from the service.

So normally we will have states…

AlbumsInitState AlbumsLoading AlbumsLoaded AlbumsListError

create a file named ‘states.dart’ and copy the below code.

import 'package:equatable/equatable.dart';

import 'package:flutter_demos/model/albums_list.dart';

@override

List<Object> get props => [];

} abstract class AlbumsState extends Equatable {List get props => []; class AlbumsInitState extends AlbumsState {} class AlbumsLoading extends AlbumsState {} class AlbumsLoaded extends AlbumsState {

final List<Album> albums;

AlbumsLoaded({this.albums});

} class AlbumsListError extends AlbumsState {

final error;

AlbumsListError({this.error});

}

Here we are extending the ‘Equatable’ class which helps to compare objects. It needs an override and we can supply properties to which the comparison needs to do to find if two objects are equal. For simplicity we will keep it empty.

Bloc for Getting Albums

Let’s create a class named “AlbumsBloc” which extends BLoC.



import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:flutter_demos/api/exceptions.dart';

import 'package:flutter_demos/api/services.dart';

import 'package:flutter_demos/bloc/albums/events.dart';

import 'package:flutter_demos/bloc/albums/states.dart';

import 'package:flutter_demos/model/albums_list.dart';class AlbumsBloc extends Bloc<AlbumEvents, AlbumsState> {



final AlbumsRepo albumsRepo;

List<Album> albums;AlbumsBloc({this.albumsRepo}) : super(AlbumsInitState());

Stream<AlbumsState> mapEventToState(AlbumEvents event) async* {

switch (event) {

case AlbumEvents.fetchAlbums:

yield AlbumsLoading();

try {

albums = await albumsRepo.getAlbumList();

yield AlbumsLoaded(albums: albums);

} on SocketException {

yield AlbumsListError(

error: NoInternetException('No Internet'),

);

} on HttpException {

yield AlbumsListError(

error: NoServiceFoundException('No Service Found'),

);

} on FormatException {

yield AlbumsListError(

error: InvalidFormatException('Invalid Response format'),

);

} catch (e) {

yield AlbumsListError(

error: UnknownException('Unknown Error'),

);

}break;

}

}

} import 'dart:io';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:flutter_demos/api/exceptions.dart';import 'package:flutter_demos/api/services.dart';import 'package:flutter_demos/bloc/albums/events.dart';import 'package:flutter_demos/bloc/albums/states.dart';import 'package:flutter_demos/model/albums_list.dart';class AlbumsBloc extends Bloc {final AlbumsRepo albumsRepo;List albums;AlbumsBloc({this.albumsRepo}) : super(AlbumsInitState()); @override Stream mapEventToState(AlbumEvents event) async* {switch (event) {case AlbumEvents.fetchAlbums:yield AlbumsLoading();try {albums = await albumsRepo.getAlbumList();yield AlbumsLoaded(albums: albums);} on SocketException {yield AlbumsListError(error: NoInternetException('No Internet'),);} on HttpException {yield AlbumsListError(error: NoServiceFoundException('No Service Found'),);} on FormatException {yield AlbumsListError(error: InvalidFormatException('Invalid Response format'),);} catch (e) {yield AlbumsListError(error: UnknownException('Unknown Error'),);}break;

In the above bloc class, you can see that we have an instance variable of ‘AlbumsRepo’.

You need to supply an initial state to the BLoC constructor.

So we have the ‘AlbumsInitState’.

The ‘mapEventToState’ is an async * function, that means it can return data without terminating the function. Here we use yield to return one of the album states at appropriate moment.

i.e when the loading starts…the above function will return AlbumsLoading state to the UI, then when the data is returned ‘AlbumsLoaded’ is returned along with the albums, then if there is any error, ‘AlbumsListError’ is returned with appropriate exceptions and finally here is our exception class.

class NoInternetException {

var message;

NoInternetException(this.message);

}class NoServiceFoundException {

var message;

NoServiceFoundException(this.message);

}class InvalidFormatException {

var message;

InvalidFormatException(this.message);

}class UnknownException {

var message;

UnknownException(this.message);

}

Dart allows to throw any class as exceptions if you want.

Screen

Now let’s create a screen that will listen to these events.

import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:flutter_demos/bloc/albums/bloc.dart';

import 'package:flutter_demos/bloc/albums/events.dart';

import 'package:flutter_demos/bloc/albums/states.dart';

import 'package:flutter_demos/model/albums_list.dart';

import 'package:flutter_demos/widgets/error.dart';

import 'package:flutter_demos/widgets/list_row.dart';

import 'package:flutter_demos/widgets/loading.dart';

@override

_AlbumsScreenState createState() => _AlbumsScreenState();

} class AlbumsScreen extends StatefulWidget {_AlbumsScreenState createState() => _AlbumsScreenState();

//

@override

void initState() {

super.initState();

_loadAlbums();

} class _AlbumsScreenState extends State {//void initState() {super.initState();_loadAlbums(); _loadAlbums() async {

context.bloc<AlbumsBloc>().add(AlbumEvents.fetchAlbums);

} @override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('Albums'),

),

body: Container(

child: _body(),

),

);

} Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Albums'),),body: Container(child: _body(),),); _body() {

return Column(

children: [

BlocBuilder<AlbumsBloc, AlbumsState>(

builder: (BuildContext context, AlbumsState state) {

if (state is AlbumsListError) {

final error = state.error;

String message = '${error.message}

Tap to Retry.';

return ErrorTxt(

message: message,

onTap: _loadAlbums,

);

}

if (state is AlbumsLoaded) {

List<Album> albums = state.albums;

return _list(albums);

}

return Loading();

}),

],

);

} Widget _list(List<Album> albums) {

return Expanded(

child: ListView.builder(

itemCount: albums.length,

itemBuilder: (_, index) {

Album album = albums[index];

return ListRow(album: album);

},

),

);

}

}

The AlbumsScreen we created has a widget called BlocBuilder which has types ‘AlbumsBloc and AlbumsState’. This widget will listen to the changes from the bloc file and update the UI.

Here you can see that the BlocBuilder has a ‘builder’ property which has a state variable that gets us access to the returned state.

So how do we trigger it.

context.bloc<AlbumsBloc>().add(AlbumEvents.fetchAlbums);

The above call will trigger the ‘fetchAlbums’ event which will trigger the BLoC and returns the state. Initially it will return AlbumLoading State, then if the call is success, it will return AlbumsLoaded state etc. Rest is simple, Update the UI based on the state. Here we are showing a list of albums when the call succeeds.

The Main Part

The BlocBuilder receives the events to update the UI. But there should be a provider, correct. So you need to wrap the AlbumsScreen with the BlocProvider. So the idea is which ever screen that wants to listen to the AlbumBloc updates should wrap itself with the BlocProvider like below.



@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Flutter Bloc Demo',

debugShowCheckedModeBanner: false,

theme: ThemeData(

primarySwatch: Colors.blue,

visualDensity: VisualDensity.adaptivePlatformDensity,

),

home: BlocProvider(

create: (context) => AlbumsBloc(albumsRepo: AlbumServices()),

child: AlbumsScreen(),

),

);

}

} class MyApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(title: 'Flutter Bloc Demo',debugShowCheckedModeBanner: false,theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: BlocProvider(create: (context) => AlbumsBloc(albumsRepo: AlbumServices()),child: AlbumsScreen(),),);

Here you can see ‘AlbumsBloc’ is receiving the ‘AlbumServices’ repo which fetches the albums.

That’s it.

Please clap if you like this post…

Remember, you can hit up to 50 claps.

Buy me a Coffee if you find my article useful

Source code

https://bitbucket.org/vipinvijayan1987/tutorialprojects/src/BlocSimple/FlutterTutorialProjects/flutter_demos/