Flutter apps communicate with APIs using the http package. You will fetch JSON, parse it into Dart model classes, and display the data in a ListView with pull-to-refresh.
flutter pub add http// lib/models/post.dart
class Post {
final int id;
final String title;
final String body;
const Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) => Post(
id: json['id'] as int,
title: json['title'] as String,
body: json['body'] as String,
);
}// lib/screens/posts_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class PostsScreen extends StatefulWidget {
const PostsScreen({super.key});
@override State<PostsScreen> createState() => _PostsScreenState();
}
class _PostsScreenState extends State<PostsScreen> {
late Future<List<Post>> _posts;
Future<List<Post>> _fetchPosts() async {
final res = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts?_limit=20'));
if (res.statusCode != 200) throw Exception('Failed to load');
final List data = jsonDecode(res.body);
return data.map((j) => Post.fromJson(j)).toList();
}
@override
void initState() { super.initState(); _posts = _fetchPosts(); }
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Posts')),
body: FutureBuilder<List<Post>>(
future: _posts,
builder: (ctx, snap) {
if (snap.connectionState == ConnectionState.waiting)
return const Center(child: CircularProgressIndicator());
if (snap.hasError)
return Center(child: Text('Error: ${snap.error}'));
return RefreshIndicator(
onRefresh: () async { setState(() => _posts = _fetchPosts()); },
child: ListView.builder(
itemCount: snap.data!.length,
itemBuilder: (_, i) => ListTile(
title: Text(snap.data![i].title),
subtitle: Text(snap.data![i].body, maxLines: 1,
overflow: TextOverflow.ellipsis),
),
),
);
},
),
);
}