BLOC Pattern in Flutter explained with Real Example
source link: https://www.coderzheaven.com/2020/10/05/bloc-pattern-in-flutter-explained-with-real-example/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
BLOC Pattern in Flutter explained with Real Example
In this article we will learn BLoc pattern in flutter for State Management with a simple real world example.
Watch Video Tutorial
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:flutter_demos/model/albums_list.dart'
;
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'
;
@override
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;
}
}
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'
;
abstract
class
AlbumsState
extends
Equatable {
@override
List<Object> 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 a 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
'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<AlbumEvents, AlbumsState> {
final
AlbumsRepo albumsRepo;
List<Album> albums;
AlbumsBloc({
this
.albumsRepo}) :
super
(AlbumsInitState());
@override
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
;
}
}
}
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'
;
class
AlbumsScreen
extends
StatefulWidget {
@override
_AlbumsScreenState createState() => _AlbumsScreenState();
}
class
_AlbumsScreenState
extends
State<AlbumsScreen> {
//
@override
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(),
),
);
}
_body() {
return
Column(
children: [
BlocBuilder<AlbumsBloc, AlbumsState>(
builder: (BuildContext context, AlbumsState state) {
if
(state is AlbumsListError) {
final
error = state.error;
String message =
'${error.message}\nTap 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.
class
MyApp
extends
StatelessWidget {
@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(),
),
);
}
}
Here you can see ‘AlbumsBloc’ is receiving the ‘AlbumServices’ repo which fetches the albums.
That’s it.
Source code
- Youtube Downloader
- Terms
- Screen
- Youtube Videos
- Video Tutorials
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK