Popular Searches
Popular Course Categories
Popular Courses

HTTP Requests in Flutter

HTTP Requests in Flutter

Flutter APIs & Networking

 


HTTP Requests in Flutter


HTTP requests are an essential part of modern Flutter applications. They allow an app to communicate with remote servers, REST APIs, databases, authentication services, payment systems, and other online services. In Flutter, the http package provides a simple and cross-platform way to make HTTP requests.


1. What is an HTTP Request?


An HTTP request is a message sent by a client application to a server to request or send information over the internet.


In a Flutter application, the Flutter app acts as the client and communicates with a backend server through HTTP requests.


Basic HTTP Communication


Flutter App
     |
     | HTTP Request
     v
Backend / API Server
     |
     | HTTP Response
     v
Flutter App

Example


Suppose a Flutter shopping application needs to display products. The application can send a GET request to an API:


GET https://example.com/api/products

The server may return JSON data containing the products.


2. Why Are HTTP Requests Used in Flutter?



  • Fetching data from REST APIs

  • Sending form data to a server

  • User login and registration

  • Fetching products, users, orders, and categories

  • Updating existing records

  • Deleting records

  • Uploading or downloading data

  • Communicating with cloud services

  • Building real-time and data-driven applications


3. HTTP Package in Flutter


The official Flutter documentation recommends the http package as a simple way to perform HTTP networking. It supports Android, iOS, macOS, Windows, Linux, and web applications.


Install the HTTP Package


flutter pub add http

Import the Package


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

The http package provides methods such as get(), post(), put(), patch(), and delete().


4. Android Internet Permission


Android applications that communicate over the internet should declare the Internet permission in the Android manifest.


 

This permission is normally placed inside the element of AndroidManifest.xml.


5. Understanding the HTTP Request Flow



  1. User performs an action in the Flutter application.

  1. Flutter creates an HTTP request.

  1. The request is sent to the API server.

  1. The server processes the request.

  1. The server returns an HTTP response.

  1. Flutter checks the response status.

  1. The response data is decoded, commonly from JSON.

  1. The application converts the data into Dart objects when appropriate.

  1. The UI displays the resulting data.


6. Common HTTP Methods









Method Purpose Typical Use
GET Retrieve data Get products or users
POST Create or submit data Create an account or order
PUT Update or replace data Update a complete user record
PATCH Partially update data Update only a user's name
DELETE Delete data Delete a product or account

7. Making a GET Request


A GET request is commonly used to retrieve information from an API.


Basic GET Example


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

 


Future fetchData() {
  return http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
  );
}


The http.get() method returns a Future because network communication is asynchronous.


8. Using async and await


Network requests should normally be handled asynchronously so the Flutter UI does not wait synchronously for the server response.


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

 


  print(response.statusCode);
  print(response.body);
}


Understanding the Code



  • async allows a function to perform asynchronous operations.

  • await waits for the HTTP request to complete.

  • response.statusCode contains the HTTP status code.

  • response.body contains the response body.


9. Understanding http.Response


The response returned by an HTTP request contains useful information from the server.








Property Description
statusCode HTTP status code returned by the server
body Response body as a string
headers HTTP response headers
request Information about the originating request when available

10. HTTP Status Codes













Status Code Meaning
200 OK - Request completed successfully
201 Created - New resource was created
204 No Content - Request succeeded without response content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Resource Not Found
500 Internal Server Error
503 Service Unavailable

11. Checking the Status Code


Always check the response status before treating the response as successful.


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

 


  if (response.statusCode == 200) {
    print('Request successful');
    print(response.body);
  } else {
    print('Request failed');
  }
}


12. Reading JSON Response Data


Most REST APIs return JSON data. Dart provides the dart:convert library for encoding and decoding JSON.


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

 


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


  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);


    print(data['id']);
    print(data['title']);
    print(data['body']);
  }
}


13. Creating a Dart Model from JSON


For larger applications, it is better to convert JSON responses into strongly typed Dart model classes instead of accessing raw maps throughout the UI.


class Post {
  final int id;
  final int userId;
  final String title;
  final String body;

 


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


  factory Post.fromJson(Map json) {
    return Post(
      id: json['id'],
      userId: json['userId'],
      title: json['title'],
      body: json['body'],
    );
  }
}


14. Converting an HTTP Response into a Model


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

 


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


  if (response.statusCode == 200) {
    final json = jsonDecode(response.body) as Map;
    return Post.fromJson(json);
  }


  throw Exception('Failed to load post');
}


15. Making a POST Request


A POST request is commonly used to create a new resource or submit data to a server.


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

 


Future createPost() async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/posts'),
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode({
      'title': 'Flutter HTTP',
      'body': 'Learning HTTP requests in Flutter',
      'userId': 1,
    }),
  );


  if (response.statusCode == 201) {
    print('Post created successfully');
    print(response.body);
  } else {
    print('Failed to create post');
  }
}


16. Understanding HTTP Headers


Headers provide additional information about an HTTP request or response.


Common Request Headers



  • Content-Type - Describes the format of the request body.

  • Accept - Indicates the response formats the client can accept.

  • Authorization - Sends authentication credentials or tokens.


Example


final response = await http.get(
  Uri.parse('https://example.com/api/products'),
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN',
  },
);

17. Sending JSON Data


When an API expects JSON, use jsonEncode() to convert a Dart object or map into a JSON string.


final requestData = {
  'name': 'Rahul',
  'email': '[email protected]',
  'age': 25,
};

 


final response = await http.post(
  Uri.parse('https://example.com/api/users'),
  headers: {
    'Content-Type': 'application/json',
  },
  body: jsonEncode(requestData),
);


18. PUT Request


PUT is commonly used to update an existing resource.


Future updatePost() async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode({
      'id': 1,
      'title': 'Updated Flutter Post',
      'body': 'Updated post content',
      'userId': 1,
    }),
  );

 


  if (response.statusCode == 200) {
    print('Post updated successfully');
  } else {
    print('Update failed');
  }
}


19. PATCH Request


PATCH is generally used when only part of a resource needs to be updated.


Future updateUserName() async {
  final response = await http.patch(
    Uri.parse('https://example.com/api/users/10'),
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'name': 'New Name',
    }),
  );

 


  if (response.statusCode >= 200 && response.statusCode < 300) {
    print('User updated');
  }
}


20. DELETE Request


DELETE requests are used to remove resources from a server.


Future deletePost(int id) async {
  final response = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
  );

 


  if (response.statusCode == 200 || response.statusCode == 204) {
    print('Post deleted successfully');
  } else {
    print('Failed to delete post');
  }
}


21. Handling Exceptions


Network requests can fail because of connectivity problems, invalid URLs, server failures, timeouts, or other conditions. Use try-catch to handle errors.


Future fetchData() async {
  try {
    final response = await http.get(
      Uri.parse('https://example.com/api/data'),
    );

 


    if (response.statusCode == 200) {
      print(response.body);
    } else {
      throw Exception('Server returned ${response.statusCode}');
    }
  } catch (e) {
    print('Request failed: $e');
  }
}


22. Using Timeout with HTTP Requests


A timeout prevents the application from waiting indefinitely for a response.


Future fetchData() async {
  try {
    final response = await http
        .get(Uri.parse('https://example.com/api/data'))
        .timeout(const Duration(seconds: 10));

 


    print(response.body);
  } catch (e) {
    print('Request failed or timed out: $e');
  }
}


23. Displaying API Data with FutureBuilder


FutureBuilder is useful for building UI based on the result of an asynchronous operation such as an HTTP request.


FutureBuilder(
  future: fetchPost(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }

 


    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }


    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    }


    return const Text('No data available');
  },
)


24. Avoid HTTP Requests Directly Inside build()


Do not create a new HTTP request directly inside the build() method because Flutter may call build() many times, which can cause repeated network requests.


Incorrect Approach


@override
Widget build(BuildContext context) {
  final future = fetchPost();
  return FutureBuilder(
    future: future,
    builder: (context, snapshot) {
      return const SizedBox();
    },
  );
}

Better Approach


late Future postFuture;

 


@override
void initState() {
  super.initState();
  postFuture = fetchPost();
}


@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: postFuture,
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const CircularProgressIndicator();
      }


      if (snapshot.hasError) {
        return Text('Error: ${snapshot.error}');
      }


      return Text(snapshot.data!.title);
    },
  );
}


25. Displaying a List from an API


When an API returns a list, convert each JSON object into a Dart model and display the models using a builder widget.


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

 


  if (response.statusCode != 200) {
    throw Exception('Failed to load posts');
  }


  final List jsonList = jsonDecode(response.body);


  return jsonList
      .map((json) => Post.fromJson(json as Map))
      .toList();
}


Displaying the List


FutureBuilder>(
  future: fetchPosts(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

 


    if (snapshot.hasError) {
      return Center(
        child: Text('Error: ${snapshot.error}'),
      );
    }


    final posts = snapshot.data ?? [];


    if (posts.isEmpty) {
      return const Center(
        child: Text('No posts found'),
      );
    }


    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];


        return ListTile(
          title: Text(post.title),
          subtitle: Text(post.body),
        );
      },
    );
  },
)


26. Query Parameters


Query parameters are values added to the URL to filter or customize an API request.


Example URL


https://example.com/api/products?category=mobile&page=1

Using Uri


final uri = Uri.https(
  'example.com',
  '/api/products',
  {
    'category': 'mobile',
    'page': '1',
  },
);

 


final response = await http.get(uri);


27. Path Parameters


Path parameters are commonly used to identify a specific resource.


final userId = 25;

 


final uri = Uri.parse(
  'https://example.com/api/users/$userId',
);


final response = await http.get(uri);


28. Authentication with HTTP Requests


Many APIs require authentication. A common approach is sending an authentication token through the Authorization header.


final response = await http.get(
  Uri.parse('https://example.com/api/profile'),
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Accept': 'application/json',
  },
);

Never hard-code real production secrets, private keys, or sensitive credentials directly into publicly distributed Flutter source code.


29. API Service Class


As an application grows, HTTP requests should be separated from UI widgets. A service class can contain API-related operations.


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

 


class ApiService {
  static const String baseUrl = 'https://example.com/api';


  Future> getPosts() async {
    final response = await http.get(
      Uri.parse('$baseUrl/posts'),
      headers: {
        'Accept': 'application/json',
      },
    );


    if (response.statusCode != 200) {
      throw Exception('Failed to load posts');
    }


    final List data = jsonDecode(response.body);


    return data
        .map((item) => Post.fromJson(item as Map))
        .toList();
  }
}


30. Separating Model, Service, and UI


A clean Flutter application can separate responsibilities into different layers.


UI
 |
 v
Controller / ViewModel
 |
 v
Repository / Service
 |
 v
HTTP Client
 |
 v
REST API
 |
 v
Server / Database

Responsibilities



  • Model: Represents application data.

  • Service: Handles communication with an external API.

  • Repository: Provides a data-access abstraction and can coordinate sources such as network and cache.

  • ViewModel/Controller: Manages UI-related state and actions.

  • UI: Displays data and accepts user interaction.


31. Loading, Success, Empty, and Error States


A good API-based Flutter screen should handle more than just the successful response.








State Example UI
Loading CircularProgressIndicator or skeleton UI
Success Display API data
Empty Display "No data found"
Error Display an understandable error message and retry option

32. Retry API Requests


Providing a retry button is useful when a temporary network or server problem occurs.


ElevatedButton(
  onPressed: () {
    setState(() {
      postFuture = fetchPost();
    });
  },
  child: const Text('Retry'),
)

33. Pull-to-Refresh


Flutter applications can use RefreshIndicator to allow users to manually refresh API data.


RefreshIndicator(
  onRefresh: () async {
    setState(() {
      postFuture = fetchPost();
    });

 


    await postFuture;
  },
  child: ListView(
    children: const [
      ListTile(
        title: Text('Pull down to refresh'),
      ),
    ],
  ),
)


34. Using http.Client


For larger applications and testing, using an http.Client can make network-dependent functions easier to test and allows the HTTP client to be injected.


Future fetchPost(http.Client client) {
  return client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
  );
}

35. Large JSON Responses


Parsing a very large JSON response can require significant CPU work. Flutter's networking documentation demonstrates moving expensive JSON parsing to another isolate with compute() when appropriate.


import 'package:flutter/foundation.dart';

 


List> parseJson(String responseBody) {
  final data = jsonDecode(responseBody) as List;


  return data
      .map((item) => item as Map)
      .toList();
}


final data = await compute(parseJson, response.body);


36. Common HTTP Request Errors



  • No internet connection

  • Invalid API URL

  • DNS or connectivity failure

  • Request timeout

  • Unauthorized request

  • Forbidden request

  • Resource not found

  • Server error

  • Invalid JSON

  • Unexpected JSON structure

  • Missing required request fields

  • Expired authentication token


37. Better Error Handling


Future fetchPost() async {
  try {
    final response = await http
        .get(
          Uri.parse('https://example.com/api/posts/1'),
        )
        .timeout(const Duration(seconds: 10));

 


    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);


      return Post.fromJson(
        data as Map,
      );
    }


    if (response.statusCode == 404) {
      throw Exception('Post not found');
    }


    if (response.statusCode == 401) {
      throw Exception('Authentication required');
    }


    if (response.statusCode >= 500) {
      throw Exception('Server error');
    }


    throw Exception('Request failed');
  } catch (e) {
    throw Exception('Unable to fetch post: $e');
  }
}


38. Complete HTTP Request Example


The following example demonstrates a complete flow: making a GET request, checking the response, decoding JSON, converting it to a model, and displaying it in Flutter.


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

 


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 json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
  }
}


Future fetchPost() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
    headers: {
      'Accept': 'application/json',
    },
  );


  if (response.statusCode == 200) {
    final data = jsonDecode(response.body)
        as Map;


    return Post.fromJson(data);
  }


  throw Exception('Failed to load post');
}


class PostScreen extends StatefulWidget {
  const PostScreen({super.key});


  @override
  State createState() => _PostScreenState();
}


class _PostScreenState extends State {
  late Future postFuture;


  @override
  void initState() {
    super.initState();
    postFuture = fetchPost();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('HTTP Request Example'),
      ),
      body: FutureBuilder(
        future: postFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState ==
              ConnectionState.waiting) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }


          if (snapshot.hasError) {
            return Center(
              child: Text('Error: ${snapshot.error}'),
            );
          }


          if (!snapshot.hasData) {
            return const Center(
              child: Text('No data found'),
            );
          }


          final post = snapshot.data!;


          return Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
              children: [
                Text(
                  post.title,
                  style: const TextStyle(
                    fontSize: 22,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 12),
                Text(post.body),
              ],
            ),
          );
        },
      ),
    );
  }
}


39. Practical API Request Flow


User taps button
       ↓
Flutter Widget
       ↓
Service / Repository
       ↓
HTTP Request
       ↓
API Server
       ↓
HTTP Response
       ↓
Check Status Code
       ↓
Decode JSON
       ↓
Dart Model
       ↓
Update State
       ↓
Flutter UI

40. Best Practices for HTTP Requests in Flutter



  • Use the http package for straightforward HTTP networking.

  • Use async and await for asynchronous requests.

  • Always check HTTP status codes.

  • Decode JSON carefully.

  • Use model classes instead of passing raw JSON throughout the UI.

  • Separate API logic from widgets.

  • Avoid making HTTP requests directly inside build().

  • Handle loading, success, empty, and error states.

  • Use timeouts for important requests.

  • Provide retry functionality when appropriate.

  • Use authentication headers when required by the API.

  • Do not expose sensitive production secrets in client-side code.

  • Use an http.Client when dependency injection and testing are beneficial.

  • Move expensive JSON parsing to an isolate when large responses require it.

  • Keep API models and networking code organized in separate files.


41. Suggested Flutter Project Structure


lib/
├── main.dart
├── models/
│   └── post.dart
├── services/
│   └── api_service.dart
├── repositories/
│   └── post_repository.dart
├── screens/
│   └── post_screen.dart
├── widgets/
│   └── post_card.dart
└── viewmodels/
    └── post_viewmodel.dart

42. Common Mistakes



  • Calling an API repeatedly from build().

  • Ignoring the HTTP status code.

  • Assuming every response contains valid JSON.

  • Not handling network exceptions.

  • Not displaying a loading state.

  • Showing technical errors directly to end users.

  • Hard-coding sensitive credentials.

  • Putting all API logic inside a widget.

  • Ignoring empty API responses.

  • Parsing huge JSON responses on the main isolate when it causes UI jank.


43. HTTP Requests vs WebSockets








HTTP WebSocket
Usually request-response communication Persistent two-way communication
Useful for REST APIs Useful for real-time communication
Common for CRUD operations Common for chat, live updates, and real-time events
Each request normally receives a response Server can send messages without a new HTTP request

44. Practice Exercise


Create a Flutter application that retrieves posts from an API and displays them in a ListView.builder.



  1. Create a new Flutter project.

  1. Add the http package.

  1. Create a Post model.

  1. Create an API service.

  1. Send a GET request.

  1. Decode the JSON response.

  1. Convert JSON objects into Post objects.

  1. Display a loading indicator.

  1. Display the posts after a successful response.

  1. Display an error message when the request fails.

  1. Add a retry button.

  1. Add pull-to-refresh functionality.


45. Interview Questions



  1. What is an HTTP request?

  1. How do you make an HTTP GET request in Flutter?

  1. What is the http package?

  1. Why does http.get() return a Future?

  1. What is the difference between GET and POST?

  1. What are HTTP status codes?

  1. How do you decode JSON in Dart?

  1. Why are model classes useful when working with APIs?

  1. How do you send JSON data using POST?

  1. What is the purpose of HTTP headers?

  1. How do you add an Authorization header?

  1. Why should API calls not normally be created directly inside build()?

  1. How does FutureBuilder work with HTTP requests?

  1. How do you handle API errors in Flutter?

  1. What is the purpose of a timeout?

  1. What is the difference between PUT and PATCH?

  1. How do you delete data from an API?

  1. Why should networking code be separated from the UI?

  1. When would you use an http.Client?

  1. When should large JSON parsing be moved to a separate isolate?


46. Quick Revision

















Concept Key Point
HTTP Protocol used for client-server communication
GET Retrieve data
POST Create or submit data
PUT Update or replace a resource
PATCH Partially update a resource
DELETE Remove a resource
http Flutter/Dart package for HTTP networking
Future Represents a value available asynchronously
jsonDecode() Converts JSON text into Dart data
jsonEncode() Converts Dart data into JSON text
FutureBuilder Builds UI based on asynchronous state
Headers Provide metadata and authentication information
Status Code Indicates the result of an HTTP request

47. Official Flutter Resources











48. Learn Flutter with JustAcademy


To learn Flutter development through structured training, explore the JustAcademy Flutter Training Course.


You can also register for a course demonstration through the JustAcademy Flutter Course Demo Registration page.


Conclusion


HTTP requests allow Flutter applications to communicate with backend servers and build data-driven experiences. By learning GET, POST, PUT, PATCH, and DELETE requests, JSON processing, authentication headers, status-code handling, error handling, asynchronous programming, and API architecture, developers can create Flutter applications that interact reliably with real-world APIs.


A clean implementation generally follows the flow: UI → Service/Repository → HTTP Request → API → HTTP Response → JSON → Model → State → UI.

 

whatsapp