Flutter BLoC (v8): How to Fetch Data From an API? - 2022 Guide
How to fetch the data from the API by using BLoC Architecture?
Introduction
- In this blog, we will see how to make an API call by using BLoC Architecture. If you remember all the way back to my article on Flutter BLoC: A Complete Guide, we went over what BLoC stands for and how it works.
- So, if you haven't checked it out make sure you check it out first. Or if you have a basic understanding of BLoC you can start right away.
The Idea
- So, first and foremost, I'm going to use a Joke API. It is a REST API that offers jokes in a consistent and well-formatted manner.
- Every time we call this API, it returns a random Joke. So, first and foremost, let's design a user interface.
- The UI is fairly basic; I used the ExpansionTile widget since the joke comes in two varieties: Setup and Delivery and Simple one-liner.
- Below it is a button labeled Load New Joke, which is used to load new jokes.
Folder Structure
- As you can see in the lib folder, there is :
- bloc folder: Responsible for managing the business logic
- data folder: It has two folders model (Responsible for creating data model classes) and repositories (Responsible for making and manipulating the data).
- presentation folder: Responsible for UI design
UI design
- The UI is very simple as I've explained above. So head over to
home.dart
file and paste the below code.class Home extends StatelessWidget { const Home({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('The Joke App'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const ExpansionTile( title: Text( "Joke", textAlign: TextAlign.center, ), children: [ Padding( padding: EdgeInsets.all(8.0), child: Text( "", style: TextStyle( fontSize: 20, ), textAlign: TextAlign.center, ), ), ], ), ElevatedButton( onPressed: () { //TODO Load New Joke }, child: const Text('Load New Joke'), ), ], ), ), ); } }
- I've used the dark theme here. The code for it is written in
main.dart
.class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.grey[900], backgroundColor: Colors.grey[900], scaffoldBackgroundColor: Colors.grey[900], buttonTheme: ButtonThemeData( buttonColor: Colors.grey[900], textTheme: ButtonTextTheme.primary, ), ), home: const Home(), ); } }
Dependencies Installation
- We need three packages:
- flutter_bloc, http, and equatable. Let's add them to the dependencies.
dependencies: flutter_bloc: ^8.0.1 http: ^0.13.4 equatable: ^2.0.3
Creating Model Class
- We are using this api to get the jokes.
- From this, we need to extract only three things: setup, delivery, and joke parameter. Below is an example of an API response.
- As you can see, this API provides two sorts of jokes: one with only one linear joke and The setup-delivery joke.
- So let's build a model that will transform this JSON data to our JokeModel.
- Head over to
lib/data/model/joke_model.dart
and paste the following code.
Fetching Jokes From API
- We'll use the
http
package to get the API from the internet. - And this will take place in the Repository folder.
Create a file called
joke_repository.dart
in the lib/data/repository folder and paste the code below into it.class JokeRepository { final String _baseUrl = "https://v2.jokeapi.dev/joke/Any"; Future<JokeModel> getJoke() async { final response = await http.get(Uri.parse(_baseUrl)); if (response.statusCode == 200) { return jokeModelFromJson(response.body); } else { throw Exception("Failed to load joke"); } } }
- As you can see, I've constructed a single method that retrieves Joke Data from the API and returns a JokeModel.
- So, the data retrieval phase is now complete. This is what we normally do in any project when we need to get data from an API. - The real part now begins, which is figuring out how to connect this to the BLoC and transmit the data to the user interface.
Building BLoC
States
- There will be just three states in our case: JokeLoadingState, JokeLoadedState, and JokeErrorState.
- When the joke is presently being fetched, the JokeLoadingState is utilized to display the Progress Indicator.
- A state with the JokeModel is the JokeLoadedState. It will provide the joke data to the user interface.
- If any errors happen during the fetching, the JokeErrorState will return an Error message.
- Let's implement it in lib/bloc/joke_bloc/joke_state.dart.
Events
- Events are nothing but different actions (button click, submit, etc) triggered by the user from UI. It contains information about the action and gives it to the Bloc to handle.
- In our case, we only have one button click, which is Load New Joke
- So let's define it inside lib/bloc/joke_bloc/joke_event.dart
Bloc
- It acts as a middle man between UI and Data layer, Bloc takes an event triggered by the user (ex: LoadNewJoke button press, Submit form button press, etc) as an input, and responds back to the UI with the relevant state.
- As you can see, I started by creating a JokeRepository. Which I've supplied as a parameter to the constructor.
- And JokeLoadingState is the first state I've passed.
- The mapEventToState function is no longer required in Version 8 of the Bloc. We just need to declare different the event body of the constructor.
- When the LoadJokeEvent is first invoked, I emit the JokeLoadingState(), as you can see.
- After that, I went to the repository and got the Joke. Then there's the fact that I've emitted JokeLoadedState.
- Additionally, if an error occurs, the JokeErrorState is emitted. It's that simple.
Providing the Repository
- To provide the JokeRepository globally we have to wrap the Home() page around RepositoryProvider in the
main.dart
file.class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( theme: (....), home: RepositoryProvider( create: (context) => JokeRepository(), child: const Home(), ), ); } }
Prodiving Bloc
- BlocProvider widget provides a bloc to its children (i.e Widgets).
- BlocProvider is used as a dependency injection (DI) widget so that a single instance of a bloc can be provided to multiple widgets within a subtree.
- Let's wrap our Home page's Scaffold around BlocProvider.
@override Widget build(BuildContext context) { return BlocProvider( create: (context) => JokeBloc( RepositoryProvider.of<JokeRepository>(context), )..add(LoadJokeEvent()), child: Scaffold( .... ) ) }
- As you can see, I used cascading to add the LoadJokeEvent. The initial state will be LoadJokeEvent, and the joke data will be displayed on the screen as soon as the screen loads.
- The JokeBloc, as you can see, also requires the JokeRepository. As a result, we must give it. To retrieve the JokeRepository, we can simply use the RepositoryProvider.of(context) method.
Rendering Widgets Based on the State - BlocBuilder
- Now it's time to render the widget in accordance with the Bloc's state.
- To do this, we must wrap our Home page body around BlocBuilder.
- Now we need to show the CircularProgressIndicator if the joke is still loading, or the actual joke after it has been obtained. Finally, the error that happened during data retrieval.
- So, let's render all of the widgets based on their state.
Final Result
Wrapping Up
- I hope you found this article to be beneficial. If you have any feedback/queries, leave them in the comments.
- Thank you for spending time reading this article. See you in the next article. So, until then...