HTTP requests in Flutter

- 👤 Andrés Cruz

🇪🇸 En español

HTTP requests in Flutter

In any application today, it's necessary to be able to communicate with the Internet to obtain data. Flutter is no exception. Whether you want to create web or mobile applications with it, you must know how to perform these types of connections or integrations. In this tutorial, we will address this topic.

Mobile applications are very distinct and usually need to connect to servers to build the app. My Academy app, both the web version made in Vue and the mobile version with Android, is an example of this. If that data obtained through an HTTP request or petition was not there, they would be useless.

In this article, you will create a sample Flutter application that uses the http package to make HTTP requests.

We left off with the fact that we learned how to show sliding modals with showModalBottomSheet in Flutter.

Why Do You Need HTTP in a Flutter App?

Today, practically any app needs to talk to a server: get data, send forms, update users, validate authentication... and Flutter is no exception. In fact, one of the reasons I've been using Flutter for years is because it integrates very well with REST APIs, regardless of whether the app is mobile or web.

Real Usage Examples

In my projects, the most common use is consuming APIs made in Laravel, CodeIgniter, or Django. In fact, if you have followed my courses, you will have already seen that almost everything we do involves HTTP connections to interconnect systems. Flutter makes this very easy with its http package.

The Academy app itself uses this package to make HTTP requests.

Principles of the HTTP Protocol [Image of client-server communication using HTTP]

HTTP is simply the language with which the "Client" (Flutter app) and "Server" (API) communicate. Every message the app sends is an HTTP request, and the API responds with data, usually in JSON format.

HTTP (Hypertext Transfer Protocol) is used for effective communication between the "Client" and the "Server", and the message a client sends to a server is known as an "HTTP request," which, if you have taken my courses, you should already know, as we always make HTTP type connections to interconnect systems, and Flutter is no exception.

Flutter supports functions like all types of HTTP requests to interconnect web systems created in Laravel, CodeIgniter... Django, Flask... or anything that uses HTTP requests with Flutter, which is a key point.

Configuring Your Flutter Project to Make HTTP Requests

Let's see step-by-step how to configure our application to make HTTP requests, as in the case of Flutter, we need to install a package to enable this operation.

1: The first step is to add the http package to the pubspec.yaml file and run pub get:

https://pub.dev/packages/http

This package allows us to easily make HTTP requests to any other application, including those we create ourselves.

dependencies: http: 

2: Import the http package:

import 'package:http/http.dart' as http;

We give it the alias of as http so that we can reference all the methods offered by this package using http.

Then run:

$ flutter pub get

3: Create a function to send a get request:

Here you can see code that is quite self-explanatory: the first thing we need is to define the connection String using a Uri, which we parse from a String to a Uri.

The next step is to make the request, which can be of any type, and in this case, it's a post request to the previous route, and we pass some parameters as an example. As you might expect, since it's a request to the internet, this takes some time that we CANNOT determine, and that's why we use an await.

Finally, we have the response and can query different data such as the status, headers, and of course, the body which brings us the response data.

var url = Uri.parse('tu-sitio');
var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'});
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
print(await http.read('https://example.com/foobar.txt'));

Explanation of the Previous Function

  • http.response (used to request data from the URL and store the response)
  • Now save the response in the response variable: response = await http.get (Uri.parse (“your-site"));
  • Here we are parsing the URL because the URL consists of raw data, and we cannot use it directly.
  • Now we have to decode or parse the JSON using json.decode (response.body) taken from the dart:convert package.
  • In the previous step, we decoded the body because the raw data is present in the response body.
  • More Methods
  • Of course, we can perform different types of connections with methods like GET, POST, PUT, PATCH, DELETE...

Types of HTTP Requests in Flutter

The most common REST APIs work with these five methods: GET, POST, PUT, PATCH, and DELETE. Here's how to use them.

GET — Retrieve Data

final res = await http.get(Uri.parse("http://10.0.2.2:1337/products"));
final List<dynamic> data = json.decode(res.body);

GET requests are the ones I use most often to load data at the beginning of a screen.

POST — Send Data

final res = await http.post(
 Uri.parse('http://10.0.2.2:1337/auth/local'),
 body: {
   "identifier": _emailController.text,
   "password": _passwordController.text,
 },
);

I use POST for login or forms. Here it's common for the API to respond with a JWT token, which I then use in other requests.

PUT / PATCH — Update Resources

To update records:

final res = await http.put(
 Uri.parse("http://10.0.2.2:1337/favorites/${user.favoriteId}"),
 body: {
   "products": json.encode(productsFavoriteId),
 },
 headers: {
   "Authorization": "Bearer ${user.jwt}"
 },
);

Here I combine body + headers.

DELETE — Eliminate Resources

await http.delete(Uri.parse('https://api.com/items/1'));

Working with JSON: Decode and Encode

  • Usar json.decode y json.encode

The server responds with plain text, so you must decode it:

final resData = json.decode(res.body);

When I send lists or maps, I always use:

json.encode(miMapa);

Mapping JSON to Dart Models

Although I sometimes print the body directly for debugging, in production, I create models using fromJson and toJson.

Error Handling and Best Practices

Check statusCode:

if (res.statusCode == 200) {
 final data = json.decode(res.body);
} else {
 print("Error: ${res.statusCode}");
}

As a recommendation, always, always check the status code, as the first time you make changes, an error occurs, and it's difficult to detect if you DO NOT first verify the server's status code. Status 401, 403, 500... happen more often than you imagine.

Timeout and Retries

Flutter allows you to set a timeout:

await http.get(url).timeout(Duration(seconds: 10));

Security: Headers and Authentication

I almost always use Bearer tokens:

headers: { "Authorization": "Bearer ${user.jwt}", }

HTTP Request Debugging and Monitoring

Logs and Tools

In development, I rely heavily on:

  • print() to inspect responses
  • Inspecting the API with Postman
  • Verifying URLs (many errors come from a simple misplaced slash)

Once, when my API was "mysteriously" failing, I ended up discovering it was a badly formatted JSON. Printing the body solved it.

Complete Example: Consuming a REST API in Flutter

Here is a typical structure I use:

HTTP Service

class ApiService {
 final baseUrl = "http://10.0.2.2:1337";
 Future<dynamic> getProducts() async {
   final res = await http.get(Uri.parse("$baseUrl/products"));
   if (res.statusCode == 200) {
     return json.decode(res.body);
   } else {
     throw Exception("Error al obtener productos");
   }
 }
}

Usage in a Widget

FutureBuilder(
 future: ApiService().getProducts(),
 builder: (_, snapshot) {
   if (snapshot.connectionState == ConnectionState.waiting) {
     return CircularProgressIndicator();
   }
   if (snapshot.hasError) return Text("Error");
   final data = snapshot.data;
   return ListView.builder(
     itemCount: data.length,
     itemBuilder: (_, i) => Text(data[i]["name"]),
   );
 },
)

Some examples that may interest you and are part of my Flutter course.

In this first example, we see how to make PUT requests to update content, passing an identifier parameter through the URL; in addition to passing a field called "products" via the body, which is simply a list of identifiers. We also see how to use the headers, in this case, to pass the authentication token:

final res = await http.put(
   Uri.parse("http://10.0.2.2:1337/favorites/${user.favoriteId}"),
   body: {
     "products": json.encode(productsFavoriteId),
   },
   headers: {
     "Authorization": "Bearer ${user.jwt}"
   });

In this second example, we see how to make a GET type request to a domain, passing an identifier and protection using an authentication token; then, we evaluate the response to know if it was satisfactory using a 200 status code:

final res = await http.get(
 Uri.parse("http://10.0.2.2:1337/favorites/${user.favoriteId}"),
 headers: {"Authorization": "Bearer ${user.jwt}"});
print(res.statusCode);
if (res.statusCode == 200) {
 final resData = json.decode(res.body);
}

In this second example, we see how to send a post request with two fields supplied in the body:

final res = await http.get(Uri.parse("http://10.0.2.2:1337/products"));
final List<dynamic> resData = json.decode(res.body);

final res = await http.post(Uri.parse('http://10.0.2.2:1337/auth/local'), body: {
 "identifier": _emailController.text,
 "password": _passwordController.text,
});

In all cases, it is important to remember that these are asynchronous requests. Any type of request that is not part of the application, but instead from external sources, such as accessing the disk, memory, or in this case, an HTTP request, is performed through asynchronous processes.

Common Errors and How to Solve Them

  • CORS
  • If you use Flutter Web, enable CORS in your API.
  • Badly Formatted JSON
  • Always print the body when something "doesn't quite add up."
  • Invalid or Expired Token
  • If you receive a 401, refresh the token.

Tips

  • Cache Responses
    • Ideal for apps that load catalogs or static lists.
  • Use More Powerful Libraries
    • Although http is sufficient for most of my projects, when I need interceptors, request cancellation, or multipart, I use dio.
  • When to Use WebSockets?
    • When you need real-time data (chat, orders, notifications).

Conclusion

Making HTTP requests in Flutter is simple, flexible, and powerful thanks to the http package. You can consume REST APIs, handle authentication, send data, update resources, and debug errors with relative ease.

When I started working with Flutter, one of the first things I noticed is that requests are totally asynchronous, just like in other modern frameworks. This prevents blocking and keeps your app fluid while receiving data from the Internet.

Frequently Asked Questions (FAQ)

  • What package do I use to make HTTP requests in Flutter?
    • Generally http, although dio is an advanced alternative.
  • How do I decode JSON responses?
    • Use json.decode(response.body).
  • How do I send authentication?
    • With headers: "Authorization": "Bearer token".
  • GET or POST?
    • GET to retrieve data, POST to send or authenticate.

Now, learn how to persist this data in Flutter using SharedPreferences.

I agree to receive announcements of interest about this Blog.

Sending HTTP requests from an app in Flutter is possible, NECESSARY and very easy with a package; it is fundamental to interconnect applications; Learn how!

| 👤 Andrés Cruz

🇪🇸 En español