flutter
/

Flutter HTTP API – Fetching Data from REST APIs

Last Sync: Today

On this page

12
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter HTTP API – Fetching Data from REST APIs

What is HTTP in Flutter?

Most modern apps need to communicate with a backend server over the internet. HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the web. In Flutter, you can make HTTP requests using the http package, which provides a simple, async API for GET, POST, PUT, DELETE, and more. This tutorial will guide you through making API calls, handling responses, and displaying data in your Flutter app.

Adding the http Package

First, add the http package to your pubspec.yaml dependencies:

dependencies:
  http: ^1.2.0

Then run flutter pub get to install it.

Making a GET Request

The simplest HTTP request is a GET. It retrieves data from a server. You use http.get() with a Uri and get a Future<Response> back.

DARTRead-only
1
import 'package:http/http.dart' as http;

Future<void> fetchData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  final response = await http.get(url);

  if (response.statusCode == 200) {
    print(response.body);
  } else {
    print('Request failed with status: ${response.statusCode}');
  }
}

The response object contains the status code (200 means success), headers, and body. The body is a raw string; you'll usually parse it as JSON.

Parsing JSON Responses

Most APIs return JSON. Use dart:convert to decode the string into a Dart object (Map or List).

DARTRead-only
1
import 'dart:convert';

Future<void> fetchPost() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  final response = await http.get(url);

  if (response.statusCode == 200) {
    Map<String, dynamic> data = jsonDecode(response.body);
    print('Title: ${data['title']}');
  } else {
    print('Error: ${response.statusCode}');
  }
}

Making a POST Request

To send data to a server, use POST. You provide a body (usually a JSON string) and set the Content-Type header to application/json.

DARTRead-only
1
Future<void> createPost() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final response = await http.post(
    url,
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'title': 'My Post',
      'body': 'This is a new post.',
      'userId': 1,
    }),
  );

  if (response.statusCode == 201) {
    print('Post created: ${response.body}');
  } else {
    print('Failed: ${response.statusCode}');
  }
}

PUT, PATCH, DELETE

DARTRead-only
1
// PUT (update entire resource)
await http.put(
  Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
  headers: {'Content-Type': 'application/json'},
  body: jsonEncode({'title': 'Updated Title'}),
);

// DELETE
await http.delete(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));

Handling Errors Gracefully

Always handle network errors and HTTP errors. Use try/catch for exceptions (like no internet) and check status codes for HTTP errors.

DARTRead-only
1
Future<List<Post>> fetchPosts() async {
  try {
    final response = await http.get(Uri.parse('https://api.example.com/posts'));
    if (response.statusCode == 200) {
      return parsePosts(response.body);
    } else {
      throw Exception('Failed to load posts (${response.statusCode})');
    }
  } catch (e) {
    throw Exception('Network error: $e');
  }
}

Displaying Data with FutureBuilder

In Flutter, you often combine HTTP requests with FutureBuilder to manage the asynchronous state and rebuild the UI when data arrives.

DARTRead-only
1
class PostsPage extends StatelessWidget {
  Future<List<Post>> _fetchPosts() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
    if (response.statusCode == 200) {
      List<dynamic> jsonList = jsonDecode(response.body);
      return jsonList.map((json) => Post.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load posts');
    }
  }

  @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 if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('No posts'));
          } else {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final post = snapshot.data![index];
                return ListTile(title: Text(post.title));
              },
            );
          }
        },
      ),
    );
  }
}

Common Mistakes

    • Not checking mounted when calling setState after an async operation: use if (mounted) to avoid memory leaks.
    • Creating the Future inside build: This causes the future to restart on every rebuild. Store it in initState or as a variable.
    • Ignoring statusCode: Always check the status code; a 200 does not guarantee success (e.g., 404 might return a page).
    • Forgetting to set Content-Type header for POST: The server may not understand the body format.
    • Not handling exceptions: Network calls can fail; always wrap in try/catch.

Best Practices

    • Use a separate service class for API calls to keep your UI clean.
    • Define models for your data (like Post class) to use type‑safe objects.
    • Use FutureBuilder for one‑time loads, and consider StreamBuilder for real‑time updates.
    • Show loading indicators while waiting for data.
    • Handle offline scenarios gracefully (e.g., show a cached version or a friendly error).

Complete Example

DARTRead-only
1
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class Post {
  final int id;
  final String title;

  Post({required this.id, required this.title});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
    );
  }
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'HTTP Demo',
      home: PostsPage(),
    );
  }
}

class PostsPage extends StatefulWidget {
  @override
  _PostsPageState createState() => _PostsPageState();
}

class _PostsPageState extends State<PostsPage> {
  late Future<List<Post>> _futurePosts;

  @override
  void initState() {
    super.initState();
    _futurePosts = fetchPosts();
  }

  Future<List<Post>> fetchPosts() async {
    final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final response = await http.get(url);

    if (response.statusCode == 200) {
      List<dynamic> jsonList = jsonDecode(response.body);
      return jsonList.map((json) => Post.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load posts');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Posts')),
      body: FutureBuilder<List<Post>>(
        future: _futurePosts,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('No posts'));
          } else {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final post = snapshot.data![index];
                return ListTile(
                  title: Text(post.title),
                );
              },
            );
          }
        },
      ),
    );
  }
}

Key Takeaways

    • Use the http package to make network requests in Flutter.
    • GET retrieves data; POST sends data; PUT/PATCH update; DELETE removes.
    • Always check response.statusCode to handle HTTP errors.
    • Parse JSON with jsonDecode from dart:convert.
    • Display async data with FutureBuilder for a reactive UI.
    • Store the future outside build to avoid repeated requests.

Try it yourself

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('HTTP Demo')),
        body: Center(child: ApiDemo()),
      ),
    );
  }
}

class ApiDemo extends StatefulWidget {
  @override
  _ApiDemoState createState() => _ApiDemoState();
}

class _ApiDemoState extends State<ApiDemo> {
  String _data = 'Press button to fetch data';
  bool _loading = false;

  Future<void> _fetchData() async {
    setState(() => _loading = true);
    try {
      final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
      final response = await http.get(url);
      if (response.statusCode == 200) {
        Map<String, dynamic> json = jsonDecode(response.body);
        setState(() => _data = 'Title: ${json['title']}');
      } else {
        setState(() => _data = 'Error: ${response.statusCode}');
      }
    } catch (e) {
      setState(() => _data = 'Network error: $e');
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        if (_loading)
          CircularProgressIndicator()
        else
          Text(_data, style: TextStyle(fontSize: 18)),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: _fetchData,
          child: Text('Fetch Post'),
        ),
      ],
    );
  }
}

Test Your Knowledge

Q1
of 4

Which function is used to send a GET request with the http package?

A
http.get
B
http.fetch
C
http.request
D
http.retrieve
Q2
of 4

What does `response.statusCode` 200 indicate?

A
Success
B
Not found
C
Server error
D
Unauthorized
Q3
of 4

How do you parse a JSON string into a Dart object?

A
json.parse()
B
jsonDecode()
C
json.encode()
D
JSON.parse()
Q4
of 4

Which widget is best for displaying data from a Future?

A
FutureBuilder
B
StreamBuilder
C
AsyncBuilder
D
FutureWidget

Frequently Asked Questions

How do I handle authentication (e.g., Bearer token)?

Add an Authorization header to your requests. For example: headers: {'Authorization': 'Bearer $token'}. You can also use the http.Client and set a Request object.

What is the difference between `http.get` and `http.Client`?

http.get is a convenience function that creates a new client each time. For multiple requests to the same host, it's more efficient to create a Client and reuse it, then close it when done.

How do I cancel an HTTP request?

The http package doesn't provide built‑in cancellation. You can use CancelableOperation from the async package, or use http.Client and close it early (which cancels ongoing requests).

How do I set a timeout for requests?

Use the timeout method on the Future: http.get(url).timeout(Duration(seconds: 5)). Catch TimeoutException to handle timeouts.

How do I handle cookies?

The http package doesn't manage cookies automatically. You can store cookies from the response and send them back in subsequent requests by setting the Cookie header. Alternatively, use a package like cookie_jar or dio with cookie support.

Previous

flutter dropdown

Next

flutter json parsing

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.