Making GET Requests & Parsing JSON Responses
Making GET Requests & Parsing JSON Responses
Almost every real-world Flutter application needs to communicate with a remote server. The most common operation is a GET request — asking the server for data — followed by parsing that data from JSON into Dart objects. In this lesson you will learn the complete workflow: add the http package, fire a GET request, inspect the response, and convert the raw JSON string into usable Dart maps and lists using dart:convert.
The http Package
Flutter does not ship with a built-in HTTP client (the dart:io HttpClient exists, but it is verbose). The community-standard solution is the http package published by the Dart team. Add it to pubspec.yaml:
pubspec.yaml dependency
dependencies:
flutter:
sdk: flutter
http: ^1.2.1 # Always check pub.dev for the latest version
Run flutter pub get to download it. Then import both libraries at the top of the Dart file that will perform the request:
Required imports
import 'package:http/http.dart' as http;
import 'dart:convert';
http with the as http alias is idiomatic. It prevents name collisions and makes it immediately clear that http.get() comes from the package, not your own code.Performing a GET Request
http.get() is an async function that returns a Future<http.Response>. You must await it inside an async function. The Uri.parse() factory converts a plain URL string into the Uri type the package expects.
Basic GET request
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> fetchUsers() async {
final uri = Uri.parse('https://jsonplaceholder.typicode.com/users');
final response = await http.get(uri);
if (response.statusCode == 200) {
// Success — the body is a JSON string
print('Response body: ${response.body}');
} else {
// Non-2xx means something went wrong
throw Exception('Failed to load users: ${response.statusCode}');
}
}
The http.Response object exposes several useful members:
response.statusCode— the HTTP status code (200, 404, 500, etc.)response.body— the response body as aStringresponse.headers— aMap<String, String>of response headersresponse.bodyBytes— the raw body asUint8List(useful for binary data)
Parsing JSON with dart:convert
The dart:convert library provides the jsonDecode() top-level function. It accepts a JSON string and returns a Dart dynamic value — either a Map<String, dynamic> (for a JSON object) or a List<dynamic> (for a JSON array). You then cast and access the fields by name.
Parsing a JSON array of objects
Future<List<Map<String, dynamic>>> fetchPosts() async {
final uri = Uri.parse('https://jsonplaceholder.typicode.com/posts');
final response = await http.get(uri);
if (response.statusCode != 200) {
throw Exception('HTTP ${response.statusCode}');
}
// jsonDecode returns dynamic — cast it to the expected shape
final List<dynamic> jsonList = jsonDecode(response.body) as List<dynamic>;
// Convert each element to a typed Map
final posts = jsonList
.map((item) => item as Map<String, dynamic>)
.toList();
for (final post in posts) {
print('Title: ${post['title']}');
print('Body: ${post['body']}');
print('User ID: ${post['userId']}');
print('---');
}
return posts;
}
fromJson factory constructor. That step is covered in the next lesson. For now, working with raw maps lets you understand what jsonDecode actually returns before adding an extra abstraction layer.Parsing a Single JSON Object
When the server returns a single JSON object (not an array), jsonDecode returns a Map<String, dynamic>. Access fields with the familiar bracket syntax:
Parsing a JSON object
Future<Map<String, dynamic>> fetchUser(int id) async {
final uri = Uri.parse('https://jsonplaceholder.typicode.com/users/$id');
final response = await http.get(uri);
if (response.statusCode != 200) {
throw Exception('User not found (${response.statusCode})');
}
final Map<String, dynamic> user =
jsonDecode(response.body) as Map<String, dynamic>;
// Access individual fields
final String name = user['name'] as String;
final String email = user['email'] as String;
final Map<String, dynamic> address =
user['address'] as Map<String, dynamic>;
final String city = address['city'] as String;
print('Name: $name, Email: $email, City: $city');
return user;
}
jsonDecode() returns dynamic. If you access a field that does not exist in the JSON, Dart returns null at runtime — it does NOT throw a compile-time error. Always check for null or use ?.cast<>() when the field may be absent, especially with optional or nullable API fields.Error Handling & Network Exceptions
Network calls can fail for reasons beyond a bad status code: no internet, DNS failure, server timeout, or a malformed JSON string. Wrap your request in a try-catch to handle both HTTP errors and socket exceptions gracefully:
Robust fetch with error handling
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io'; // for SocketException
Future<List<dynamic>> safeFetchPosts() async {
try {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'))
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
return jsonDecode(response.body) as List<dynamic>;
} else {
throw Exception('Server error: ${response.statusCode}');
}
} on SocketException {
throw Exception('No internet connection');
} on FormatException {
throw Exception('Bad response format — could not parse JSON');
}
}
Summary
To make a GET request and parse JSON in Flutter: add http to your dependencies, import http and dart:convert, call await http.get(Uri.parse(url)), check response.statusCode == 200, and pass response.body to jsonDecode(). The result is either a Map<String, dynamic> or a List<dynamic> that you can iterate or cast. Wrap the call in a try-catch to handle connectivity issues and malformed responses.
http.get() + jsonDecode() form the foundation of all Flutter networking. Every higher-level pattern — model classes, repositories, FutureBuilder integration — builds on top of these two primitives.