An excellent guide to HTTP request handling with Flutter Bloc

The Bloc State Management approach is more often in the news than ever before. Due to its remarkable way of managing complex data in the form of streams. Moreover, the Bloc pattern, now, is termed the recommended approach for developers out there. Moreover, an architectural pattern like MVVM, MVC, etc. with Bloc State management is the right recipe for an updated Flutter Developer/Engineer. However, this blog post is about – An excellent guide to HTTP request handling with Flutter Bloc. Let’s begin…


Table of Contents

– Project structure

We shall be parsing an API that fetches the Provinces of Pakistan with their itineraries.

We shall be implementing the Clean Architecture approach. Create a new Flutter project under the lib directory, and create the following folders:

Let’s move to the next section and install the required dependencies.

– Installing Plugins

We shall need the following plugins:

flutter_bloc: Flutter Bloc is an extended version of the bloc plugin and contains instances of both Bloc and Cubit.

equatable: Equatable is used to compare objects.

http: To parse the request.

We are done with the initial phase. Let’s get to the implementation section.

– Implementation

Note: Your special attention is required here.

Classification of folders

  • Endpoints (API Strings)
  • Service Class (API parsing)
  • Repository (Data Layer)
  • Bloc (Events and States)
  • UI (Presentation)

Note: If you’re at the mid-stage of learning Flutter and haven’t explored this one yet. This might sound confusing and boring too if understood, but, the more you divide the code into chunks the better it will be readable. That is what a Bloc does.

– Service Class

Inside the model folder, create a new file province_model.dart and add this code.

import 'dart:convert';
List<ProvinceModel> guideModelFromJson(String str) => List<ProvinceModel>.from(json.decode(str).map((x) => ProvinceModel.fromJson(x)));
String guideModelToJson(List<ProvinceModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class ProvinceModel {
  ProvinceModel({
    this.province,
    this.capital,
    this.itineraries,
  });
String? province;
  String? capital;
  List<Itinerary>? itineraries;
factory ProvinceModel.fromJson(Map<String, dynamic> json) => ProvinceModel(
    province: json["province"],
    capital: json["capital"],
    itineraries: List<Itinerary>.from(json["itineraries"].map((x) => Itinerary.fromJson(x))),
  );
Map<String, dynamic> toJson() => {
    "province": province,
    "capital": capital,
    "itineraries": List<dynamic>.from(itineraries!.map((x) => x.toJson())),
  };
}
class Itinerary {
  Itinerary({
    this.place,
  });
String? place;
factory Itinerary.fromJson(Map<String, dynamic> json) => Itinerary(
    place: json["place"],
  );
Map<String, dynamic> toJson() => {
    "place": place,
  };
}

Note: You can use any sort of JSON TO DART converter plugins available or use third-party resources.

Under the endpoints folder, create a new file as api_endpoints.dart and add the following snippet:

class APIEndpoints {

  static final APIEndpoints end = APIEndpoints._();
  factory APIEndpoints() => end;

  APIEndpoints._();

  static const String apiUrl = 'api_url_here';
  static const String token = 'token_here';

  static const String province = '/provinces';
}

Moreover, in the service folder itself, create a file services.dart. In addition, create a class named APIService.

This class will be responsible for the function that returns the response List<ProvinceModel>.

Here is the code snippet:

class ApiService {
  Future<List<ProvinceModel>> fetchGuide() async {
    try {
      dynamic response = await http.get(Uri.parse("URL_HERE"));
         return guideModelFromJson(response);
    } catch (e) {
      return [];
    }
  }
}

Next, under the repository folder, create a file province_repository.dart, and add the following code:

class ProvinceRepository {
  ApiService? service = ApiService();

  Future<List<ProvinceModel>> get fetchGuide async => await service!.fetchGuide();
}

This repository pattern will act as a data layer in fetching the response from the API and passing them to the bloc for presentation (UI).

– The Bloc Pattern

A Bloc needs to be equipped with Events and States to trigger the continuous flow of data and manage it.

After we declare the repository, we will create two separate files under the bloc folder i.e:

  • province_events.dart
  • province_states.dart

Furthermore, these two files shall have the following pattern:

Creating an abstract event class, and some subclasses extending the abstract one.

Creating an abstract state class, and some subclasses extending the abstract one.

1- province_events.dart

Code snippet

import 'package:equatable/equatable.dart';

abstract class ProvinceEvents extends Equatable {}

class ProvinceLoaderEvent extends ProvinceEvents {
  @override
  // TODO: implement props
  List<Object?> get props => [];
}

class ProvinceLoadedEvent extends ProvinceEvents {
  @override
  // TODO: implement props
  List<Object?> get props => [];
}

In the above snippet, the abstract class is being extended by Equatable, which means that each subclass will have an override props function, that will be able to compare the objects, reducing many boilerplate codes. Furthermore, they contain two sub-events, whether the API response is parsed successfully or vice versa.

2- province_states.dart

Code snippet

import 'package:equatable/equatable.dart';
import 'package:demo_project/models/province_model.dart';

abstract class ProvinceStates extends Equatable {}

class ProvinceLoaderStates extends ProvinceStates {
  @override
  // TODO: implement props
  List<Object?> get props => [];
}

class ProvinceLoadedStates extends ProvinceStates {
  final List<ProvinceModel> repo;
  ProvinceLoadedStates(this.repo);
  
  @override
  // TODO: implement props
  List<Object?> get props => [repo];
}

Similarly, as of province_events.dart, two state subclasses, either the data model converted successfully to the presentation or vice versa.

At this stage, create a new file province_bloc.dart and add the following code snippet:

class ProvinceBloc extends Bloc<ProvinceEvents, ProvinceStates> {
ProvinceRepository? repository;

ProvinceBloc(this.repository) : super(ProvinceLoaderStates()) {
   on<ProvinceLoadedEvent>((state, emit) async {
      emit(ProvinceLoaderStates());
      await Future.delayed(const Duration(seconds: 2));
      List<ProvinceModel> data = await repository!.fetchGuide;
      emit(ProvinceLoadedStates(data));
   });
 }
}

The above snippet represents the following:

on<EventName> method that takes two params:

  • state (current), emit (new)
  • code structure to fetch the response from the server

Furthermore, the ProvinceRepositiory has been required to get the latest response each time this bloc class is called. Secondly, provide the initial state to the class i.e. ProvinceLoaderStates().

So, up till now, we have gone through service, repository, model, and bloc approaches, it’s time to consume this request into our presentation layer (UI).

– The Presentation Layer

To make use of the bloc, we need to create its instance with BlocProvider.

Bloc Provider

Provides accessibility of Bloc instance within the application. In addition, it also handles the lifecycle of Blocs, creating and disposing of them to avoid memory leaks and enhancing app performance.

BlocProvider<ProvinceBloc>(
 create: (context) => ProvinceBloc(),
 child: Scaffold(
    appBar: AppBar(),
  ),
);

So, we got the idea regarding BlocProvider. Now, to distribute the data to the UI widgets, we need the following:

  • BlocBuilder<Bloc, State> – used to show only Widgets in the presentation
  • BlocListener<Bloc, State> – used to show content like messages, toasts, etc, but not Widgets
  • BlocConsumer<Bloc, State> – used to show both widgets followed by other stuff

Since, for this project, I’ll be making use of BlocBuilder, as we don’t want any extra stuff.

Code snippet

body: BlocBuilder<ProvinceBloc, ProvinceStates>(
        builder: (_, state) {
          if (state is ProvinceLoaderStates) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          } else if (state is ProvinceLoadedStates) {
            return ListView(
                children: state.repo.map(
                      (e) => InkWell(
                        child: buildView(context, e, style),
                        onTap: () => Navigator.pushNamed(
                            context, APIConstants.provinceItinerary,arguments: e),
                      ),
                    ).toList());
          }
          return Container();
        },
      ),
    ),
  );


  Widget buildView(BuildContext context, ProvinceModel e, TextStyle style) {
    return Padding(
      padding: const EdgeInsets.all(2.0),
      child: Stack(
        fit: StackFit.loose,
        clipBehavior: Clip.none,
        children: [
          ClipRRect(
            borderRadius: const BorderRadius.all(Radius.circular(25)),
            child: Image.network("https://wallpapercave.com/wp/wp3470406.jpg",
                fit: BoxFit.cover),
          ),
          Positioned(
              bottom: 10,
              left: 10,
              child: Text(
                "Capital " + e.capital!,
                style: style.copyWith(fontSize: 15),
              )),
          Positioned(
              bottom: 30,
              left: 30,
              child: Text(
                e.province!,
                style: style,
              )),
        ],
      ),
    );
  }
}

This snippet is complete, and our App will run too, but we won’t see any data. Because we haven’t attached the event that would emit the loadProvinceState.

Modifying the above snippet

BlocProvider<ProvinceBloc>(
 create: (context) => ProvinceBloc()..add(ProvinceLoadedEvent()),

/// rest of the code

We’re done with the task.

A big thanks for your precious time.


Wrapping up

Hope you enjoyed every bit of it.

In this blog post: An excellent guide to HTTP request handling with Flutter Bloc, I tried my best, up to my limited knowledge, to make this fit for a beginner to a pro-level developer.

Moreover, If you think I missed out on something or want to add your point of view, please jot it down in the comments section below. I would love to know that as well.

Furthermore, I would greatly appreciate it if you could propose an engaging Flutter topic suitable for an intriguing blog post.

Also, you can connect with me on:

Link to GitHub Repositories, and YouTube Channel

Read out previous blogs – Click here

Also, read Top Flutter Interview questions that should be helpful

Leave a Comment