Fetching Data with http
In React you reach for fetch or axios to make HTTP requests — they are either built into the browser or installed as a single package. Flutter has no built-in HTTP client, so the ecosystem settled on the http package from the Dart team. The mental model is the same: fire a request, await the response, inspect the status code, and decode the body. The differences are at the API surface.
Side-by-side comparison
Section titled “Side-by-side comparison”Note: Live network calls may be blocked in the DartPad sandbox — run the real
httpexample locally after addinghttp: ^1.2.0to yourpubspec.yamldependencies.
// React — fetch (built-in) or axiosimport axios from 'axios';
// Using fetchasync function getUser(id) { const res = await fetch(`https://api.example.com/users/${id}`); if (!res.ok) throw new Error(`HTTP error: ${res.status}`); const data = await res.json(); return data;}
// Using axios (throws automatically on non-2xx)async function getUserAxios(id) { const { data } = await axios.get( `https://api.example.com/users/${id}` ); return data;}// Flutter — package:httpimport 'dart:convert';import 'package:http/http.dart' as http;
Future<Map<String, dynamic>> getUser(int id) async { final uri = Uri.parse( 'https://api.example.com/users/$id', ); final response = await http.get(uri);
if (response.statusCode != 200) { throw Exception('HTTP error: ${response.statusCode}'); }
// response.body is a String — decode manually final data = jsonDecode(response.body) as Map<String, dynamic>; return data;}Runnable example
Section titled “Runnable example”The DartPad below skips the live network call and instead uses Future.delayed to simulate async latency — identical to how you would mock an API in a Jest test. The JSON decoding logic is real dart:convert code.
import 'dart:convert';
import 'package:flutter/material.dart';
// Simulates a network call — returns after 1 second
// In production replace this with http.get(uri)
Future<Map<String, dynamic>> fetchUser() {
return Future.delayed(
const Duration(seconds: 1),
() => jsonDecode('{"id": 1, "name": "Ada Lovelace", "email": "[email protected]"}')
as Map<String, dynamic>,
);
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HTTP Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const UserScreen(),
);
}
}
class UserScreen extends StatelessWidget {
const UserScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Fetch User'),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
body: FutureBuilder<Map<String, dynamic>>(
future: fetchUser(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text(
'Error: ${snapshot.error}',
style: const TextStyle(color: Colors.red),
),
);
}
final user = snapshot.data!;
return Center(
child: Card(
margin: const EdgeInsets.all(24),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 28,
vertical: 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Colors.indigo,
child: Text(
'${user['name'][0]}',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 12),
Text(
user['name'] as String,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
_Row(label: 'ID', value: '${user['id']}'),
const SizedBox(height: 8),
_Row(label: 'Email', value: user['email'] as String),
],
),
),
),
);
},
),
);
}
}
class _Row extends StatelessWidget {
final String label;
final String value;
const _Row({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 56,
child: Text(
'$label:',
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Colors.indigo,
),
),
),
Expanded(child: Text(value)),
],
);
}
}