Bloc Pattern In Flutter || Part 2💙

What did we learn in the 1st part? 🤔

  • What is Bloc?
  • What are Events and States?
  • How does the stream works?
  • What is BlocProvider, BlocBuilder and BlocConsumer? (Going to refresh your memory on this though 😉)

Packages that we will use:

  • equatable: Equatable overrides == and hashCode for you so you don't have to waste your time writing lots of boilerplate code.
  • flutter_bloc: flutter_bloc is our main package that is made to make our task easy to implement bloc in Flutter. It is made to work with the official package: bloc
  • http: This package contains a set of high-level functions and classes that make it easy to consume HTTP resources. It’s multi-platform and supports mobile, desktop, and browser. So, mainly we will use this package for the HTTP Requests.
  • bloc file: This file contains the main Business Logic
  • events file: This file states all the events that are present in your app.
  • state file: This file contains all the states that your app undergoes.
  • BlocProvider: BlocProvider is the class/widget that will provide the instance of Bloc to the child subtree. In most cases, BlocProvider should be used to create new blocs which will be made available to the rest of the subtree. In this case, since BlocProvider is responsible for creating the bloc, it will automatically handle closing it. Syntax:
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
  • BlocBuilder: BlocBuilder widget is responsible for building/re-building the child subtree whenever there is a state change. It requires a bloc and a builder function. BlocBuilder is very similar to StreamBuilder but has a more simple API to reduce the amount of boilerplate code needed. Syntax:
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)
  • BlocListener: BlocListener is a Flutter widget that takes a BlocWidgetListener and an optional bloc and invokes the listener in response to state changes in the bloc. It should be used for functionality that needs to occur once per state change such as navigation, showing a SnackBar, showing a Dialog, etc... listener is only called once for each state change (NOT including the initial state) unlike builder in BlocBuilder and is a void function. Syntax:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
  • BlocConsumer: BlocConsumer is a mixture of BlocBuilder and BlocListener. It exposes a builder and listener to react to new states. BlocConsumer is analogous to a nested BlocListener and BlocBuilder but reduces the amount of boilerplate needed. BlocConsumer should only be used when it is necessary to both rebuild UI and execute other reactions to state changes in the bloc. Syntax:
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)

Too much theory done, isn’t it? So now, let’s do some real stuff!!

equatable: ^1.2.5
flutter_bloc: ^6.1.1
http: ^0.12.2
Base URL: https://01b0f7a1-9563-47f5-b628-f14c51464ec5.mock.pstmn.io/api/get-weather-success?city=
class Weather {
int temperature;
int yesterdayMax;
int yesterdayMin;
int todayMax;
int todayMin;
String city;
Weather({
this.city,
this.temperature,
this.yesterdayMax,
this.yesterdayMin,
this.todayMax,
this.todayMin,
});
}
import 'dart:convert';
import 'package:bloc_weather_example/screens/constants.dart';
import 'package:bloc_weather_example/model/weather.dart';
import 'package:http/http.dart' as http;
class ApiService {
static Future getWeather(String city) async {
String requestUrl = https://01b0f7a1-9563-47f5-b628-f14c51464ec5.mock.pstmn.io/api/get-weather-success?city=$city;
http.Response response = await http.get(requestUrl);
if (response.statusCode == 200) {
var decodedData = jsonDecode(response.body);
Weather _weather = Weather(
city: city,
temperature: decodedData['temperature'],
yesterdayMax: decodedData['yesterdayMax'],
yesterdayMin: decodedData['yesterdayMin'],
todayMax: decodedData['todayMax'],
todayMin: decodedData['todayMin']);
return _weather;
} else {
print(response.statusCode);
}
}
}
part of 'weather_bloc.dart';@immutable
abstract class WeatherEvent {}
class WeatherInitialEvent extends WeatherEvent {}class WeatherGetEvent extends WeatherEvent {
final String city;
WeatherGetEvent({this.city});
}
part of 'weather_bloc.dart';@immutable
abstract class WeatherState {}
class WeatherInitial extends WeatherState {}class WeatherLoadingState extends WeatherState {}class WeatherGetState extends WeatherState {}class WeatherLoadedState extends WeatherState {
final Weather weather;
WeatherLoadedState({this.weather});
}
import 'dart:async';import 'package:bloc/bloc.dart';
import 'package:bloc_weather_example/model/weather.dart';
import 'package:bloc_weather_example/services/api_service.dart';
import 'package:meta/meta.dart';
part 'weather_event.dart';
part 'weather_state.dart';
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
WeatherBloc() : super(WeatherInitial());
@override
Stream<WeatherState> mapEventToState(WeatherEvent event) async* {
if (event is WeatherGetEvent) {
yield WeatherLoadingState();
Weather _weather = await ApiService.getWeather(event.city);
yield WeatherLoadedState(weather: _weather);
}
}
}
import 'package:bloc_weather_example/bloc/weather_bloc.dart';
import 'package:bloc_weather_example/screens/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(MyApp());class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
debugShowCheckedModeBanner: false,
home: BlocProvider(
create: (context) => WeatherBloc(),
child: Home(),
),
);
}
}
import 'package:bloc_weather_example/bloc/weather_bloc.dart';
import 'package:bloc_weather_example/screens/weather_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
TextEditingController _city = TextEditingController();
@override
Widget build(BuildContext context) {
return Stack(
children: [
Image.asset(
'assets/bg.jpg',
fit: BoxFit.cover,
),
Scaffold(
backgroundColor: Colors.transparent,
body: BlocBuilder<WeatherBloc, WeatherState>(
builder: (BuildContext context, state) {
if (state is WeatherLoadingState) {
print('Weather Loading State');
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
state is WeatherLoadedState
? WeatherCard(
state: state,
)
: Container(),
SizedBox(
height: 30.0,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _city,
style: TextStyle(color: Colors.black, fontSize: 20.0),
decoration: InputDecoration(
fillColor: Colors.white70,
filled: true,
hintText: 'Enter City Name',
hintStyle: TextStyle(color: Colors.black26),
prefixIcon: Icon(Icons.home),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
borderSide: BorderSide(
color: Colors.white,
width: 2.0,
),
),
),
keyboardType: TextInputType.name,
),
),
SizedBox(
height: 20.0,
),
Container(
height: 50.0,
width: 200.0,
child: RaisedButton(
child: Text(
'Check Weather!',
style: TextStyle(color: Colors.white),
),
color: Colors.blue.shade400,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
),
onPressed: () async {
context.read<WeatherBloc>().add(
WeatherGetEvent(city: _city.text),
);
},
),
),
],
),
);
},
),
),
],
);
}
}
context.read<WeatherBloc>().add(WeatherGetEvent(city: _city.text));
import 'package:bloc_weather_example/bloc/weather_bloc.dart';
import 'package:bloc_weather_example/screens/constants.dart';
import 'package:flutter/material.dart';
class WeatherCard extends StatelessWidget {
final WeatherLoadedState state;
WeatherCard({this.state});
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
side: new BorderSide(color: Colors.blue, width: 3.0),
borderRadius: BorderRadius.circular(10.0),
),
color: Colors.white60,
elevation: 10.0,
shadowColor: Colors.yellow,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'City: ' + state.weather.city.toString() + ' 🏡',
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
Text(
'Current Temperature: ' + state.weather.temperature.toString(),
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
Text(
'Today\'s Maximum: ' + state.weather.todayMax.toString(),
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
Text(
'Today\'s Minimum: ' + state.weather.todayMin.toString(),
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
Text(
'Yesterday\'s Maximum: ' + state.weather.yesterdayMax.toString(),
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
Text(
'Yesterday\'s Minimum: ' + state.weather.yesterdayMin.toString(),
style: weatherCardTextStyle,
),
SizedBox(
height: 10.0,
),
],
),
),
);
}
}
TextStyle weatherCardTextStyle = TextStyle(
color: Colors.black,
fontSize: 20.0,
);

Don’t stop, until you are breathing!💙
- Abhishek Doshi

--

--

--

Google Developer Expert — Dart, Flutter & Firebase 💙💛

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Let’s build a movie review django app

Be a Pro Gamer with Monopolon

How to represent graphs in memory ?

Hey SSML

Where to Learn MongoDB in 2022

Laravel Model Multiple languages’ Attributes

Tata 1mg Project

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abhishek Doshi

Abhishek Doshi

Google Developer Expert — Dart, Flutter & Firebase 💙💛

More from Medium

Flutter : Widget and its lifecycle

Firestore Database — Flutter💙💛

Introduction to Flutter

Building a Calorie Tracker App in Flutter!