Introduction
Fetching and displaying data from a REST endpoint is a common requirement in modern mobile apps. In this tutorial, you’ll learn how to integrate a Flutter REST API, parse JSON, and render it in your UI. We’ll use the http package to make HTTP requests, decode JSON, and populate a ListView. This approach works equally well for public APIs or your custom backends.
Setting Up Your Project
Create a new Flutter project:
flutter create flutter_rest_api_demo
cd flutter_rest_api_demo
Add the HTTP dependency in pubspec.yaml:
dependencies:
flutter:
sdk: flutter
http
Run flutter pub get to install.
Defining the Data Model
Suppose our endpoint returns a list of posts in this JSON format:
[
{
"userId": 1,
"id": 1,
"title": "Hello World",
"body": "This is a sample post."
},
…
]Create a Dart class to map JSON to objects.
class Post {
final int userId;
final int id;
final String title;
final String body;
Post({required this.userId, required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
userId: json['userId'],
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}Fetching JSON Data
Import http and dart:convert. Create an async function to call the RESTful API in Flutter:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'post.dart';
Future<List<Post>> fetchPosts() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
List jsonList = json.decode(response.body);
return jsonList.map((jsonItem) => Post.fromJson(jsonItem)).toList();
} else {
throw Exception('Failed to load posts');
}
}This snippet demonstrates:
Making a GET request
Checking statusCode
Decoding JSON with json.decode
Converting maps to Post instances
Displaying Data in a ListView
Use FutureBuilder to integrate HTTP requests into your Flutter UI:
import 'package:flutter/material.dart';
import 'post.dart';
import 'api_service.dart';
class PostListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Posts')),
body: FutureBuilder<List<Post>>(
future: fetchPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final posts = snapshot.data!;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return ListTile(
title: Text(post.title),
subtitle: Text(post.body),
);
},
);
}
},
),
);
}
}Key points:
FutureBuilder handles async states
Displays a loader, error message, or a populated list
Uses a Flutter REST API integration pattern
Error Handling and Best Practices
Always check response.statusCode to handle HTTP errors.
Use try-catch if you expect network issues.
Consider using Flutter Bloc, Provider, or Riverpod for state management as your API integrations grow.
Cache responses locally for offline scenarios with packages like hive or shared_preferences.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
In conclusion, with this foundation, you can expand to POST, PUT, DELETE requests, secure your API calls, or integrate authentication. Remember, effective error handling and state management can greatly improve your app’s reliability and user experience.