Build Your First App with Flutter: Fetching Data from an API

Since the release of Flutter Beta, its community has been growing steadily! The goal of this article is to show how you can use Flutter to build applications in record time. It won’t make you a Flutter senior developer, but it will show you the main principles for building a small real-world Flutter app.

What is Flutter?

Flutter is a cross-platform mobile development framework written in Dart by the Google team. It is targeted toward Android and iOS.

“Flutter provides you a library for crafting high-quality native looking interfaces on iOS and Android in record time. Flutter works with existing code as well, and it is used by developers and organizations around the world. Even though it is still in alpha but that doesn’t stop anyone adopting it.”

Now that you know what Flutter is, I recommend that you install it by following this guide from Flutter Docs.

What we’re going to build in this post

We’ll be implementing a simple mobile app for beer fanatics, which consumes the following JSON API to display a list of beers:

https://api.punkapi.com/v2/beers

Step 1: Starting the project

I’m assuming that you already know how to create a project using the wizard from your IDE, so let’s create one named top_beers. After that, let’s delete the auto-generated code from the main.dart file and start from scratch.

First, we need to setup a few basic things like:

  • The main function to bootstrap our application, in main.dart,

  • The main widget for the Flutter app, in app.dart,

  • A widget for the home screen, in home.dart, which will display our list of beers.

import 'package:flutter/material.dart';
import 'screens/home.dart';

class BeerListApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Beer List App',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      primaryColor: Colors.black,
      accentColor: Colors.black
    ),
    home: Home(),
  );
}

import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      centerTitle: true,
      title: Text('Top Beers'),
    ),
  );
}

import 'package:flutter/material.dart';
import 'app.dart';

void main() => runApp(BeerListApp());

The main.dart file contains the main function responsible for running our application and instantiating a BeerListApp object where a MaterialApp will be built upon.

The MaterialApp class in app.dart registers a mobile app and gives us a set of options to enforce Material Design standards such as the title position and the color scheme. Similarly, the Scaffold in home.dart helps us create common elements in mobile applications automatically. Here, we use it to position our AppBar, and later in this post, we’ll use it to configure the body of the content area. It’s important to note that the _HomeState.build method is the hook that actually builds our widget.

Notice that we extend StatelessWidget in the BeerListApp class and StatefulWidget in the Home class. You should use StatefulWidget when the widget is supposed to store a state that can be manipulated with user interaction, which is the case for the beers represented by the Home widget. If you are creating a widget that doesn’t need to store any data to manage the state, choose StatelessWidget instead.

Here’s what our code affords us so far — an empty screen with the “Top Beers” title:

Step 2: Models and repository fetch

This step involves creating a repository to parse the JSON data received from the API and transform it into instances of the Beer model.

But wait,what is Repository? In the context of our application, a Repository encapsulates persistence from the external world (e.g., an API) by fetching data only when necessary. To do that, the repository looks up the data in a cache provider named DB Provider. If not found, it fetches the data over HTTP by means of an API Provider. After that, it caches the data for future requests.

As a first step, we need to know how to represent each beer of the JSON payload. We’ll create a named constructor that receives a Map<String, dynamic> property where String references the data key and dynamic references the data value (In Dart, dynamic means the value can be of any type):

class Beer {
  final int id;
  final String name;
  final String tagline;
  final String description;
  final String image_url;

  Beer.fromJSON(Map<String, dynamic> jsonMap) :
    id = jsonMap['id'],
    name = jsonMap['name'],
    tagline = jsonMap['tagline'],
    description = jsonMap['description'],
    image_url = jsonMap['image_url']; 
}

The next step is to fetch the data from the /beers endpoint, so let’s add http as a dependency on pubspec.yml:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  http: ^0.12.0

dev_dependencies:
  flutter_test:
    sdk: flutter
flutter:
  uses-material-design: true

After editing this file, don’t forget to run flutter packages get in your terminal to update the project dependencies.

import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/beer.dart';

Future<Stream<Beer>> getBeers() async {
 final String url = 'https://api.punkapi.com/v2/beers';

 final client = new http.Client();
 final streamedRest = await client.send(
   http.Request('get', Uri.parse(url))
 );

 return streamedRest.stream
     .transform(utf8.decoder)
     .transform(json.decoder)
     .expand((data) => (data as List))
     .map((data) => Beer.fromJSON(data));
}

Now let’s work on the repository. The instruction on line 9 dispatches the HTTP request and awaits a response from the /beers endpoint. As in JavaScript, Dart also has the async/await feature.

After obtaining a response, we apply a few methods to the resulting stream to make it ready for consumption. The first transform on line 14 decodes the stream to UTF-8. Without it, we’d get nothing but raw bytes. After that, we parse the decoded contents as JSON and then expand it into a List to prepare it for the map function. Finally, we map each item of the list into a Beer object.

Step 3: Display the list of beers

To display the list of beers we first need to fetch them, so let’s start by importing the model and the repository in home.dart. Inside _HomeState.initState, we’ll listen for the stream of Beer objects and push them onto the _beers list as they come in:

import '../repository/beer_repository.dart';
import '../models/beer.dart';

...

class _HomeState extends State<Home> {
  List<Beer> _beers = <Beer>[];

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

  void listenForBeers() async {
    final Stream<Beer> stream = await getBeers();
    stream.listen((Beer beer) =>
      setState(() =>  _beers.add(beer))
    );
  }
  ....
}

After having the beers properly fetched and saved in our list, we need to create a widget for each beer. Let’s pass a body option to the Scaffold:

...

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      centerTitle: true,
      title: Text('Top Beers'),
    ),
    body: ListView.builder(
      itemCount: _beers.length,
      itemBuilder: (context, index) => BeerTile(_beers[index]),
    ),
  );

...

ListView.builder describes the body of our widget. The itemBuilder key holds a function that returns a Widget for each beer, and itemCount represents the number of items to be shown on screen.

The following diagram pictures the entire flow:

So, when our Home class is constructed, initState is invoked and calls getBeers to fetch the data from the API. It keeps listening for Beer objects that will be then pushed onto our list, while setState instructs Flutter to update the UI.

And that’s it

Wrapping up

Congrats, you’ve built your first app using Flutter to consume an API, but don’t stop here! Although the project was something basic, just imagine all the possibilities to work with Flutter, like moving on to learn how to store data using SQLite or go deeper into the use of streams in Dart. The complete code for what we made is available on GitHub https://github.com/hjJunior/flutter-fetch-data-from-api

In this post, we didn’t use a set of patterns called BLoC (Business Logic Component) which is an alternative to MVP or Redux architectures. If you want to know more about BLoC or others architectures you can check these links:

And to finish, if you’re interested in knowing more about all the possibilities you have by using Flutter, check the Flutter Live Top 5 Recap.