Making HTTP Requests in Flutter
Networking is a huge part of most apps. You need to fetch data from APIs, send user input to servers, and handle responses. The http package makes this straightforward in Dart.
First, add the http package to your pubspec.yaml:
dependencies:
http: ^1.1.0
Now let's make a GET request to fetch some data:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<List<Map<String, dynamic>>> fetchPosts() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
if (response.statusCode == 200) {
return List<Map<String, dynamic>>.from(jsonDecode(response.body));
} else {
throw Exception('Failed to load posts');
}
}
Notice the async and await keywords. HTTP calls take time, so Dart treats them as asynchronous operations. The await pauses execution until the server responds.
For POST requests, you send data to a server:
Future<Map<String, dynamic>> createPost(String title, String body) async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': title,
'body': body,
'userId': 1,
}),
);
if (response.statusCode == 201) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to create post');
}
}
The headers tell the server we're sending JSON. The body is encoded with jsonEncode to turn our Dart Map into a JSON string.
Handling loading and error states in your widget is crucial for a good user experience. You can use a FutureBuilder or manage state manually:
class PostList extends StatefulWidget {
const PostList({super.key});
@override
State<PostList> createState() => _PostListState();
}
class _PostListState extends State<PostList> {
late Future<List<Map<String, dynamic>>> _postsFuture;
@override
void initState() {
super.initState();
_postsFuture = fetchPosts();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Map<String, dynamic>>>(
future: _postsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final posts = snapshot.data!;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
return ListTile(title: Text(posts[index]['title']));
},
);
}
},
);
}
}
This pattern gives your users clear feedback while data is loading and graceful error handling if something goes wrong.
Try it Yourself ->