Working with APIs
Learning Objectives
- You know of the http library and you know how to use an API in a Dart program.
HTTP library
For programmatic API access, we use the http library, which is used for making HTTP requests.
If you’re unsure what an HTTP request is, skim the chapters Introduction to the Internet and HTTP Protocol from the Web Software Development course.
To use http
, we need to add it to the dependencies (the pubspec.yaml
file) of the project, and import it to the Dart files where it is used.
dependencies:
# new line:
http: 1.2.2
The library provides functions for making HTTP requests, such as get and post — get
is used for retrieving information, while post
is used for sending information.
To represent addresses, we use Dart’s Uri class, which provides a convenience method parse for creating an instance of an address.
The methods get
and post
are asynchronous, and return an instance of a Response, wrapped in a Future. To wait for the completion of the future and to gain access to the response, we use the await
keyword.
Retrieving data from an API
The following program demonstrates how to retrieve a joke from the Joke API at https://simple-joke-api.deno.dev/random. The example first creates an instance of a Uri
from a string, then uses the get
function to retrieve a joke, and finally prints the joke, which is stored in the body
of the response.
import 'package:http/http.dart' as http;
Future<void> main() async {
final url = Uri.parse('https://simple-joke-api.deno.dev/random');
final response = await http.get(url);
print(response.body);
}
When run locally, the above program outputs e.g. the following joke.
{
"setup":"Why do programmers write code in the dark?",
"punchline":"Because light attracts bugs."
}
As you notice might notice, the response is a string that is formatted as a JSON document. JSON is a common format for data exchange, consisting of key-value pairs, where the key is a string and the value can e.g. be a string, number, boolean, or an array.
The async/await
syntax allows structuring asynchronous code in a similar way to synchronous code. The syntax first appreared in C#
around 2011 and 2012, and has been since adopted by many programming languages.
When using async functions, the function must be marked with the async
keyword, and the return type has to be a Future
that wraps the actual return type, e.g. Future<void>
.
When a string-formatted JSON document is retrieved, it can be parsed into a more useful format using the dart:convert library. Importing the library brings the functions jsonDecode and jsonEncode to the program.
In the following, we first retrieve the joke, then parse the response, and finally print the joke.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final url = Uri.parse('https://simple-joke-api.deno.dev/random');
final response = await http.get(url);
final map = jsonDecode(response.body);
print(map['setup']);
print(map['punchline']);
}
When run locally, the above program outputs a joke in two parts. The output is, e.g., as follows.
Why do programmers write code in the dark?
Because light attracts bugs.
API encapsulation
In practice, we would encapsulate APIs to separate classes that would be used to access them. For the Joke API, we would encapsulate the API into a class called, for example, JokeService
, which would provide a method for retrieving random jokes. Jokes would also be represented using a class, which would provide us a vocabulary to work with.
In the following example, we have a class JokeService
that provides a method getRandomJoke
. Further, we also have a Joke
class that represents a joke. Now, from the point of view of the API user — the main
method, the usage of the API is straightforward.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final joke = await JokeService().getRandomJoke();
print('${joke.setup} -- ${joke.punchline}');
}
class JokeService {
Future<Joke> getRandomJoke() async {
final response = await http.get(
Uri.parse('https://simple-joke-api.deno.dev/random'),
);
final data = jsonDecode(response.body);
return Joke.fromJson(data);
}
}
class Joke {
String setup;
String punchline;
Joke()
: setup = '',
punchline = '';
Joke.fromJson(Map<String, dynamic> map)
: setup = map['setup'],
punchline = map['punchline'];
}
Posting data to an API
Posting data is done using the post function of the http library. The function post
is given an instance of a Uri
, possible request headers, and the body of the request. The example below shows the key parts of a post
request, where the function jsonEncode is used to transform a given map to a string-formatted JSON-document.
var address = 'https://api.address';
var headers = {'Content-Type': 'application/json; charset=UTF-8'};
var content = {'key': 'value'};
await http.post(
Uri.parse(address),
headers: headers,
body: jsonEncode(body)
);
Let’s look at this using the Sum API at https://fitech-api.deno.dev/sum-api. The API accepts JSON-formatted data where the data must contain the fields one
and two
, which have numeric values. The API returns a JSON-formatted document with the field sum
that contains the sum of the sent values.
For example, if the JSON-document sent to the server contains the values 1
and 2
for the fields one
and two
.
{
"one": 1,
"two": 2
}
The response will have a field sum
with the value 3
.
{
"sum": 3
}
The API would be used as follows.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final requestData = {'one': 1, 'two': 2};
final response = await http.post(
Uri.parse('https://fitech-api.deno.dev/sum-api'),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: jsonEncode(requestData),
);
final responseData = jsonDecode(response.body);
print(responseData['sum']);
}