Articles

REST API in Flutter: A beginner’s guide to fetching data

This Flutter API tutorial shows you how to fetch data from a REST API and display it in your app. You’ll build a working posts application using the http package, learning to make API requests, parse JSON responses, and create dynamic UIs with live data.

Here’s what you’ll accomplish:

  • Set up a Flutter project with the http package for API integration
  • Create data models to parse JSON from the API endpoint
  • Build a service layer to handle API requests
  • Display fetched data in a scrollable ListView

A REST API (Representational State Transfer) enables applications to communicate with servers and receive structured data in JSON format. Popular apps like X and Instagram use REST APIs to constantly refresh content. We’ll recreate this by connecting to JSONPlaceholder API, a free testing service that provides mock data for learning.

Note: This Flutter API tutorial introduces three foundational concepts: asynchronous data fetching, JSON parsing, and reactive UI updates. These principles form the backbone of every data-driven app you’ll build.

  • This course will introduce learners to the Flutter framework with interactive lessons on basic app components.
    • Beginner Friendly.
      1 hour
  • Explore OpenAI’s API and learn how to write more effective generative AI prompts that help improve your results.
    • Beginner Friendly.
      < 1 hour

Prerequisites for Flutter API integration

Before beginning, make sure our system includes:

Building the Flutter API app

Project setup

We’ll begin by creating a new Flutter project. In a terminal or IDE, run:

flutter create api_app

Once created, open the project and organize its structure for scalability. Inside the lib directory, create subfolders for models, services, and UI:

Flutter rest api app structure

Now, open pubspec.yaml and add the following dependency to enable API communication:

dependencies:
flutter:
sdk: flutter
http: ^1.2.1

Then run:

flutter pub get

This installs the HTTP package, which allows Flutter to make network requests. Before we start with the implementation, let’s see how exactly the process of rendering the API will look like.

Workflow diagram explaining how API works in Flutter

Note: Defining a clear project architecture early keeps large applications organized. As our app grows, this foundation will make debugging and feature updates much simpler.

Understanding the API and creating the model

We’ll use the JSONPlaceholder API, which provides mock endpoints for posts, comments, and users. Our endpoint for this project will be:

https://jsonplaceholder.typicode.com/posts

Opening this URL in a browser shows a JSON array similar to:

{
"userId": 1,
"id": 1,
"title": "Sample Title",
"body": "This is a sample post body."
}

To work with this data in Flutter, we’ll need to define a Dart model. A model that can convert raw JSON into structured objects that are easier to manipulate.

Let’s create the model file. Inside the models folder, create a file named post.dart and add the following code inside it:

import 'dart:convert';
List<Post> postFromJson(String str) =>
List<Post>.from(json.decode(str).map((x) => Post.fromJson(x)));
class Post {
int userId;
int id;
String title;
String? body;
Post({
required this.userId,
required this.id,
required this.title,
this.body,
});
factory Post.fromJson(Map<String, dynamic> json) => Post(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"],
);
}

In this code, the Post class defines the structure of a single post object.

  • The userId and id uniquely identify the post.
  • The title and body fields store the content.
  • The postFromJson() function converts a JSON string into a list of Post objects.

Creating a model ensures our app can handle structured, type-safe data instead of raw strings or maps.

Note: For larger APIs, using a generator such as quicktype.io can automatically create Dart models from JSON.

Creating the service layer

To keep the code clean, we’ll separate our API logic into a dedicated service layer. This class handles all HTTP requests to the REST API, keeping the UI focused solely on presentation. Inside the services folder, create a file named remote_service.dart and add the following code to it:

import 'package:http/http.dart' as http;
import 'package:api_tutorial/models/post.dart';
class RemoteService {
Future<List<Post>?> getPosts() async {
var client = http.Client();
var uri = Uri.parse('https://jsonplaceholder.typicode.com/posts');
var response = await client.get(uri);
if (response.statusCode == 200) {
var json = response.body;
return postFromJson(json);
}
return null;
}
}

In this code:

  • The class RemoteService contains the method getPosts(), which uses the http client to make a GET request.
  • When the response returns with status code 200 (success), the raw JSON is passed through our postFromJson() method to produce a list of Post objects.
  • The Future<List<Post>?> ensures the function can run asynchronously without blocking the UI.

Here, what we have done is separate API calls into a service layer, which is a best practice. It improves readability, testing, and reusability, especially when multiple screens require the same data source.

Building the UI (the view)

With our data ready, it’s time to display it on screen. Let’s create our main screen where posts will appear. Inside the views folder, create a new file named home_page.dart and then add the following code to it:

import 'package:flutter/material.dart';
import 'package:api_tutorial/models/post.dart';
import 'package:api_tutorial/services/remote_service.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Post>? posts;
var isLoaded = false;
@override
void initState() {
super.initState();
getData();
}
getData() async {
posts = await RemoteService().getPosts();
if (posts != null) {
setState(() {
isLoaded = true;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Flutter API Example")),
body: Visibility(
visible: isLoaded,
replacement: const Center(child: CircularProgressIndicator()),
child: ListView.builder(
itemCount: posts?.length,
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
posts![index].title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
posts![index].body ?? '',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
),
);
}
}

Here’s what’s happening in this code:

  • The class _HomePageState holds two key variables:

    • List<Post>? posts; to store the data
    • bool isLoaded = false; to track loading state
  • In initState(), we call getData(), which fetches posts from the API. Once data is received, setState() updates isLoaded to true, triggering a UI rebuild.

  • The Visibility widget toggles between a loading spinner and a list of posts once data is ready.

Note: Always provide feedback during asynchronous operations. A loading indicator prevents confusion and ensures a better user experience.

Connecting the API service to Flutter app

Finally, we’ll tie everything together in our main app entry file. Open the main.dart file and replace its content with:

import 'package:flutter/material.dart';
import 'views/home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter API Tutorial',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}

Running the app (flutter run) should now launch a clean interface that lists post titles and bodies fetched directly from the API.

Compiling the Flutter app showing live REST API data

Note: If your app displays an empty screen, check the API endpoint, internet connection, or ensure the http dependency was properly installed.

Conclusion

We’ve successfully built a functional Flutter app that fetches live data, parses JSON into Dart objects, and displays it dynamically. This Flutter API tutorial covered essential concepts for building data-driven mobile apps.

Key skills you learned:

  • Making asynchronous HTTP requests to REST APIs
  • Handling JSON data using model classes
  • Managing state during API calls
  • Structuring Flutter apps with service layers
  • Implementing error handling for network failures

For a deeper dive into backend fundamentals, explore the free Introduction to Backend Course on Codecademy.

Frequently asked questions

1. What is a REST API, and why do we use it in Flutter apps?

A REST API (Representational State Transfer API) allows applications to communicate with external servers using HTTP requests. In Flutter, we use REST APIs to fetch real-time data such as posts, weather updates, or user information and display it dynamically in our app’s UI. This makes our application interactive and data-driven instead of being static.

2. Why do we need a model class to handle API data?

A model class converts raw JSON data from an API into structured Dart objects. This helps maintain type safety, makes the code cleaner, and simplifies the process of displaying and manipulating data in the UI. Without models, handling nested JSON structures would become error-prone and harder to maintain.

3. How do I use API in Flutter?

To use an API in Flutter, add the http package to your pubspec.yaml file, then create model classes for your data structure. Use async/await with methods like http.get() or http.post() to make API requests. Parse the JSON response into your Dart objects using the model’s fromJson() method, then display the data in your UI using widgets like ListView or FutureBuilder.

4. Which API is best for Flutter?

The best API package for Flutter depends on your project requirements. The http package is ideal for simple REST API calls and is officially recommended by the Flutter team. For more advanced features like request interceptors, timeout handling, and better error management, the dio package is excellent. If you’re working with GraphQL APIs, use the graphql_flutter package instead.

5. How to call HTTP in Flutter?

To call HTTP in Flutter, first import the http package with import 'package:http/http.dart' as http;. Then create an async function and use await http.get(Uri.parse('your-api-url')) to make a GET request. Check the response.statusCode to verify success (200 means successful), then parse the response.body which contains the JSON data. Always wrap HTTP calls in try-catch blocks to handle network errors.

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team

Learn more on Codecademy

  • This course will introduce learners to the Flutter framework with interactive lessons on basic app components.
    • Beginner Friendly.
      1 hour
  • Explore OpenAI’s API and learn how to write more effective generative AI prompts that help improve your results.
    • Beginner Friendly.
      < 1 hour
  • Learn how to develop APIs using Swagger tooling and the OpenAPI specification.
    • With Certificate
    • Intermediate.
      < 1 hour