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.
We want to work with you. Check out our "What We Do" section!