Simple Data Fetching in Flutter

Mario Gunawan
8 min readDec 1, 2022

--

This tutorial is intended for people that have learned a bit of flutter, and want to try to expand their knowledge. This project will use the http library to fetch data.

Hello everyone, in this tutorial, we will create a really simple pokemon app by fetching a list of pokemon from an external API in Flutter. This application will showcase you how to do simple data fetching using Flutter. Here’s a demo of the app that we will build:

Demo App

Create the App

Create a new fluter project by writingflutter create pokemon_app in your terminal. Or, if you are using vscode create a new flutter project by typing cmd/ctrl + shift + p then selecting Flutter: New Project . A new flutter code will be built for you, open that directory in an IDE.

Then, go to the lib/main.dart file and remove everything. Replace it with this boilerplate:

import 'package:flutter/material.dart';

void main() {
runApp(const App());
}

class App extends StatelessWidget {
const App({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "",
home: Scaffold(
appBar: AppBar(
title: const Text("Pokemon App"),
),
body: const Text("Hello World"),
));
}
}

Tadah! Your boilerplate code is done and we are ready to move on to the next part, getting text from the network!

Fetching pokemon from the network

To fetch pokemon (pokemons?) from the network, we must create a model class that we will link to the text that we got from the api. Create a new file, called pokemon.dart, and put in this code:

class Pokemon {
const Pokemon({required this.name, required this.url});

final String name;
final String url;
}

This class will be used for binding our data to a class so we can do some manipulation later. After that, we need to import a library called http which will help us fetch data from the internet. In your pubspec.yaml, add this line anywhere in the dependencies part:

# pubspec.yaml

dependencies:
...
http: ^0.13.5

Then run flutter pub get in the terminal to re-fetch your dependencies.

Now, we will fetch some data from the internet

Create a new file, pokemon_fetch.dart, then put in this code:

import 'dart:convert';

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

const baseUrl = "https://pokeapi.co/api/v2/pokemon";

Future<List<Pokemon>> fetchListOfPokemon() async {
try {
final response = await http.get(Uri.parse(baseUrl));
final requestSuccessful = response.statusCode == 200;

if (requestSuccessful) {
var pokemonsJson =
jsonDecode(response.body)["results"] as List<dynamic>;

return pokemonsJson.map((pokemonJson) {
return Pokemon(name: pokemonJson["name"], url: pokemonJson["url"]);
}).toList();
}

return [];
} catch (exception) {
print(exception);
return [];
}
}

Let’s explore this code line per line:

import ‘package:http/http.dart’ as http;

This will import the http package, and give alias to the entire file as http. So if there’s a get method in the file, we can call http.get, not just get(…). The purpose of this is to make the code clearer, since calling get(…) is not clear enough.

Future<List<Pokemon>> fetchListOfPokemon() async {

If you come from Javascript background, future is similar to Promise(). Basically, this means that the function will return something, but it will be returned sometime in the future. The <List<Pokemon>> indicates that we are returning a list of pokemon in the future. The async keyword is there to help us write a cleaner Future code. This is really hard to explain in a paragraph, so if you have time, do read about Future and async in dart.

var pokemonsJson = jsonDecode(response.body)["results"] as List<dynamic>;

The pokemon(s) that we fetch from the server by http.get will be in string (this might be different for different library), so we need to convert it to JSON. We only get the “results” part since it’s the only relevant part. For context, you can open https://pokeapi.co/api/v2/pokemon and view the data yourself:

Fetch result from https://pokeapi.co/api/v2/pokemon

In case you’re wondering, I’m using an extension to format JSON better so it looks cooler. If your fetch result looks different, then maybe this article is outdated by then. Keep me updated on this by contacting me through medium / email.

Since the fetching part is done, onto the next part, showing the data in your application!

Show Data Using FutureBuilder

Now that the data fetching is done, create a new file in the /lib directory called pokemon_page.dart and put in this code.

import 'package:flutter/material.dart';
import 'package:pokemon_app/pokemon.dart';
import 'package:pokemon_app/pokemon_fetch.dart';

class PokemonPage extends StatefulWidget {
const PokemonPage({super.key});

@override
State<PokemonPage> createState() => _PokemonPageState();
}

class _PokemonPageState extends State<PokemonPage> {
late final Future<List<Pokemon>> futurePokemon;

@override
void initState() {
super.initState();

futurePokemon = fetchListOfPokemon();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Pokemon App"),
),
body: FutureBuilder(
future: futurePokemon,
builder: (context, snapshot) {
if (snapshot.hasData) {
var pokemons = snapshot.data!;

return ListView.separated(
itemBuilder: (context, index) => ListTile(
title: Text(pokemons[index].name),
),
separatorBuilder: ((_, __) => const SizedBox(
height: 8,
)),
itemCount: pokemons.length);
}
if (snapshot.hasError) {
return Text("Oops an error happened: ${snapshot.error}");
}

return const CircularProgressIndicator();
},
),
);
}
}

Whew, that’s a lot of code. Most of the early stuffs are boilerplate code, so let’s talk about some important lines:

late final Future<List<Pokemon>> futurePokemon;

We declare this futurePokemon as a state because we want to only do the fetching once. If we put this in the build() method, then everytime flutter re-renders and call the build() method, then it will re-fetch and we don’t quite want that behavior.

body: FutureBuilder(
future: futurePokemon,

We declare a FutureBuilder, which is a flutter component that will render based on a Future. So it can render X if the future is still fetching, or Y if success, or Z if failure, more easily. In the following explanation, this will become clearer.

builder: (context, snapshot) {
if (snapshot.hasData) {
var pokemons = snapshot.data!;

return ListView.separated(
itemBuilder: (context, index) => ListTile(
title: Text(pokemons[index].name),
),
separatorBuilder: ((_, __) => const SizedBox(
height: 8,
)),
itemCount: pokemons.length);

builder: (context, snapshot) {
if (snapshot.hasData) {

The code above declares a builder, a function that expects us to return a widget. Inside the function, we check first if the snapshot, or the result of the Future hasData. If yes, then we can be sure that snapshot.data contains List<Pokemon> and that’s why we put ! (exclamation) mark that means that we are sure the data is not null.

return ListView.separated(

Then, we create a ListView, that helps us to show a list. We use the ListView.separated factory to have some spaces between list element. =

itemBuilder: (context, index) => ListTile(
title: Text(pokemons[index].name),
),

The itemBuilder is responsible for rendering individual list item. The builder provides a context, which we will not use, and an index. We will use the index to get the pokemon, then displays its name.

separatorBuilder: ((_, __) => const SizedBox(
height: 8,
)),

The separatorBuilder is responsible for creating separator which separates between list elements. We create an empty box with height 8 here to make an 8 pixels buffer between one list element and another.

itemCount: pokemons.length);

lastly, we must declare the length of the list, or else either there will be an error or ineffective list rendering.

if (snapshot.hasError) {
return Text("Oops an error happened: ${snapshot.error}");
}

return const CircularProgressIndicator();

After that, we declare if the snapshot has an error, we returns a text that explains the error (kinda, could be better). And then if there is no error, or no data, then we return a CircularProgressIndicator to indicate that we are loading.

Phew, now that we are done with the hardest part, let’s display our newly created widget onto the screen!

Use The Widget In Main.dart

Add import on top of your main.dart file

...
import 'package:pokemon_app/pokemon_page.dart';

and then replace the body of the build() function to this (replace everything):

return MaterialApp(title: "", home: PokemonPage());

Now run the app by typing flutter run , this should be what you get:

Kinda finished app

Optional: Style your list

Now that you have successfully created the pokemon list app, there’s some improvement to be made, namely, the design…. I mean it’s not horrible, but it’s just too plain. Let’s decorate this list. Open your pokemon_page.dart and change the itemBuilder part to this:

itemBuilder: (context, index) {
final color = index % 2 == 0
? Colors.lightBlueAccent
: Colors.lightBlue;

return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(pokemons[index].name),
tileColor: color,
contentPadding: const EdgeInsets.all(8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
);
},

That’s a lot of code, and actually I’m not going to explain it, since the code itself is pretty explanatory. You can learn more about designing by googling, so by any means, google anything until you get the design you want! The code above produce this preview:

Actually, after a second vieweing, this design might be worse XD

Optional: Make the item open the pokemon url when tapped

For this part, you will need to add another library to your application. In your pubspec.yaml, in dependencies, add url_launcher:

# pubspec.yaml
dependencies:
# ...
url_launcher: ^6.1.7

Now, in your pokemon_page.dart file, in the ListTile() part, add a new property onTap. The function that you declare on onTap will be called when user clicks the ListTile. The onTap property within the ListTile now looks like this:

ListTile (
onTap: () {
launchUrl(Uri.parse(pokemons[index].url));
}
// ...
)

Now, you must stop your app (ctrl + c in terminal) then run it again. Now whenever you tap a pokemon, it will send you to an url with that pokemon’s data.

The data doesn’t look that pretty :( but at least this showcases the library’s functionality

That’s It!

I intend to make this tutorial into a series with each article improves the functionality of this app a bit further. Let me know what you think in the comment about this.

If you get lost in the middle or somewhere, this is the final code for this tutorial: https://github.com/margunwa123/flutter_pokemon/tree/finished_1

Thanks for reading~ Happy learning!

--

--

Mario Gunawan

I'm a mobile / web developer. I'm mostly writing articles about software development.