Fetch data from the internet
Refactor your application to use a robust, production-ready API client for the Wikipedia CLI.
In Chapter 3, you used the http package to make a simple request.
Now, you'll revisit HTTP to
build a robust, production-ready API client within the wikipedia package.
You'll learn how to handle query parameters,
map JSON responses to your data models, and
export these functions for use in your CLI.
What you'll accomplish
Prerequisites
#Before you begin this chapter, ensure you:
-
Have completed Chapter 10 and have a
working Dart development environment with the
dartpediaproject. - Understand basic networking concepts (like APIs and HTTP requests).
- Understand basic data serialization formats such as JSON.
Tasks
#
In this chapter, you'll move beyond simple scripts and
implement a proper API layer.
You'll work within the wikipedia package to
implement the API client logic, which
improves your application's scalability and maintainability.
Task 1: Add the http dependency to the wikipedia package
#
To make HTTP requests, you need to
add the http package as a dependency to the wikipedia package.
Open the
wikipedia/pubspec.yamlfile within your project.Locate the
dependenciessection.-
Add
http: ^1.3.0(or the latest stable version) underdependencies.yamldependencies: http: ^1.3.0 Save the
pubspec.yamlfile.Run
dart pub getin your terminal from thewikipediadirectory.
Task 2: Implement Wikipedia API calls
#Next, you'll create the API functions to fetch data from Wikipedia. You'll create three files:
-
summary.dart: This file will contain functions for retrieving article summaries. -
search.dart: This file will handle search queries to find articles. -
get_article.dart: This file will contain functions for fetching the full content of an article.
Create the directory
wikipedia/lib/src/api.Create the file
wikipedia/lib/src/api/summary.dart.-
Add the following code to
wikipedia/lib/src/api/summary.dart:dartimport 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../model/summary.dart'; Future<Summary> getRandomArticleSummary() async { final http.Client client = http.Client(); try { final Uri url = Uri.https( 'en.wikipedia.org', '/api/rest_v1/page/random/summary', ); final http.Response response = await client.get(url); if (response.statusCode == 200) { final Map<String, Object?> jsonData = jsonDecode(response.body) as Map<String, Object?>; return Summary.fromJson(jsonData); } else { throw HttpException( '[WikipediaDart.getRandomArticle] ' 'statusCode=${response.statusCode}, body=${response.body}', ); } } on FormatException { // todo: log exceptions rethrow; } finally { client.close(); } } Future<Summary> getArticleSummaryByTitle(String articleTitle) async { final http.Client client = http.Client(); try { final Uri url = Uri.https( 'en.wikipedia.org', '/api/rest_v1/page/summary/$articleTitle', ); final http.Response response = await client.get(url); if (response.statusCode == 200) { final Map<String, Object?> jsonData = jsonDecode(response.body) as Map<String, Object?>; return Summary.fromJson(jsonData); } else { throw HttpException( '[WikipediaDart.getArticleSummary] ' 'statusCode=${response.statusCode}, body=${response.body}', ); } } on FormatException { // todo: log exceptions rethrow; } finally { client.close(); } }This code defines two functions:
getRandomArticleSummaryandgetArticleSummaryByTitle. Both functions use thehttppackage to make GET requests to the Wikipedia API and return aSummaryobject.getRandomArticleSummaryfetches a summary for a random article, whilegetArticleSummaryByTitlefetches a summary for a specific article title. Next create the file
wikipedia/lib/src/api/search.dart.-
Add the following code to
wikipedia/lib/src/api/search.dart:dart import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../model/search_results.dart'; Future<SearchResults> search(String searchTerm) async { final http.Client client = http.Client(); try { final Uri url = Uri.https( 'en.wikipedia.org', '/w/api.php', <String, Object?>{ 'action': 'opensearch', 'format': 'json', 'search': searchTerm, }, ); final http.Response response = await client.get(url); if (response.statusCode == 200) { final List<Object?> jsonData = jsonDecode(response.body) as List<Object?>; return SearchResults.fromJson(jsonData); } else { throw HttpException( '[WikimediaApiClient.getArticleByTitle] ' 'statusCode=${response.statusCode}, ' 'body=${response.body}', ); } } on FormatException { rethrow; } finally { client.close(); } }This code defines the
searchfunction, which uses thehttppackage to make a GET request to the Wikipedia API'sopensearchendpoint and returns aSearchResultsobject. Theopensearchendpoint is used to search for Wikipedia articles based on a search term. Create the file
wikipedia/lib/src/api/get_article.dart.-
Add the following code to
wikipedia/lib/src/api/get_article.dart:dartimport 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../model/article.dart'; Future<List<Article>> getArticleByTitle(String title) async { final http.Client client = http.Client(); try { final Uri url = Uri.https( 'en.wikipedia.org', '/w/api.php', <String, Object?>{ // order matters - explaintext must come after prop 'action': 'query', 'format': 'json', 'titles': title.trim(), 'prop': 'extracts', 'explaintext': '', }, ); final http.Response response = await client.get(url); if (response.statusCode == 200) { final Map<String, Object?> jsonData = jsonDecode(response.body) as Map<String, Object?>; return Article.listFromJson(jsonData); } else { throw HttpException( '[ApiClient.getArticleByTitle] ' 'statusCode=${response.statusCode}, ' 'body=${response.body}', ); } } on FormatException { // TODO: log rethrow; } finally { client.close(); } }This code defines the
getArticleByTitlefunction, which uses thehttppackage to make a GET request to the Wikipedia API and returns aList<Article>object. This function retrieves the content of a Wikipedia article based on its title.
Task 3: Export the API functions
#
Now that you've created the API functions,
you need to export them from the wikipedia library so that
they can be used by the cli package.
You'll also export the existing models.
Open the
wikipedia/lib/wikipedia.dartfile.-
Add the following
exportstatements to the file:dartexport 'src/api/get_article.dart'; export 'src/api/search.dart'; export 'src/api/summary.dart'; export 'src/model/article.dart'; export 'src/model/search_results.dart'; export 'src/model/summary.dart'; export 'src/model/title_set.dart';These
exportstatements make the API functions and models available to other packages that depend on thewikipediapackage.
Task 4: Verify with tests
#
Now that you have implemented the API functions and
updated the package dependencies, it's good practice to
run the tests you created in the previous chapter.
This will confirm that your changes have not
broken the existing functionality of the wikipedia package.
Open your terminal and navigate to the
wikipedia/testdirectory.-
Remove the default test file by running the command
rm wikipedia_test.dart(on macOS or Linux) ordel wikipedia_test.dart(on Windows). This file was generated automatically but is not used in our project. Open your terminal and navigate to the
wikipediadirectory.-
Run the command
dart test.You should see output similar to this, confirming all your existing tests still pass:
bash00:02 +3: All tests passed! This confirms that the wikipedia package is still working as expected.
Review
#What you accomplished
Here's a summary of what you built and learned in this lesson.Constructed complex URIs with query parameters
You used Uri.https() with a query parameter map to construct API URLs. This approach properly encodes special characters and handles complex query parameter combinations.
Created a production-ready API client
You implemented a variety of functions that properly manage the HTTP client lifecycle with try
and finally, and maps the raw JSON responses to typed Dart objects.
Exported functions and types for reuse
You added export statements in wikipedia.dart to expose both the API functions and data model types. This creates a public library that your
cli package can import and use directly.
Quiz
#Check your understanding
1 / 3Uri.https?
-
Pass them as a third argument map:
Uri.https('api.com', '/search', {'q': 'dart'})That's right!
The third parameter accepts a
Map<String, dynamic>of query parameters. Dart handles URL encoding automatically. -
Append them to the path string:
Uri.https('api.com', '/search?q=dart')Not quite.
Query parameters in the path string work but aren't the cleanest approach.
-
Use string concatenation:
Uri.https('api.com', '/search') + '?q=dart'Not quite.
You can't concatenate strings to
Uriobjects using+.Uriprovides a different mechanism for adding query parameters. -
Call
.addQueryParam('q', 'dart')on theUriobject.Not quite.
Uriobjects are immutable and don't have anaddQueryParammethod. Query parameters must be specified differently.
client.get(url), how should you check if the request was successful?
-
Check if
response.statusCode == 200(or another success code).That's right!
HTTP status codes indicate success (200-299) or various failures (400s, 500s). Always check before processing the response body.
-
Check if
response.bodyis not empty.Not quite.
An empty body could be valid (like a 204 No Content). The body content alone doesn't tell you if the request succeeded.
-
Wrap the call in try/catch as successful requests don't throw.
Not quite.
HTTP errors (such as 404 and 500) don't throw exceptions by default. The request completes normally even when the server returns an error.
-
Check if
response.isSuccessreturns true.Not quite.
There's no
isSuccessproperty onResponse. You need to examine a different property of the response object.
finally block to call client.close(). Why is this important?
-
To ensure network resources are released even if an exception occurs.
That's right!
finallyruns whether the try block succeeds or throws. This ensures the client's connections are properly closed, preventing resource leaks. -
To save the response data to disk before the function returns.
Not quite.
close()doesn't save data anywhere. Saving data would require explicit file operations. -
To send a 'connection closed' message to the server.
Not quite.
While closing affects the connection, the main purpose is client-side cleanup, not communicating with the server.
-
To clear the response from memory and trigger garbage collection.
Not quite.
Dart's garbage collector handles memory automatically.
close()releases a different kind of resource.
Next lesson
#
In the next lesson, you'll complete the CLI by
integrating the wikipedia package with the cli package.
You'll implement the command logic and display the results to the user.
Unless stated otherwise, the documentation on this site reflects Dart 3.10.3. Page last updated on 2026-1-8. View source or report an issue.