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 because they allow an app to communicate with remote servers, REST APIs, databases, authentication services, and other backend systems. Flutter commonly uses the http package to perform HTTP requests such as GET, POST, PUT, PATCH, and DELETE.

The http package provides a simple and widely used approach for making cross-platform HTTP requests in Flutter applications.


1. What Are HTTP Requests?

HTTP stands for Hypertext Transfer Protocol. It is a communication protocol used to transfer data between a client and a server.

In a Flutter application:

  • The Flutter application acts as the client.
  • The backend or API acts as the server.
  • The client creates and sends an HTTP request.
  • The server receives and processes the request.
  • The server sends an HTTP response.
  • The Flutter application processes the response and updates the UI.

Basic HTTP Flow

Flutter App
    ↓
HTTP Request
    ↓
Backend / API Server
    ↓
HTTP Response
    ↓
Flutter App
    ↓
Process Data
    ↓
Display Data

2. Why Are HTTP Requests Used in Flutter?

HTTP requests allow Flutter applications to work with data that is stored or processed outside the application.

Common examples include:

  • User registration and login
  • Fetching products
  • Displaying news articles
  • Loading user profiles
  • Submitting forms
  • Uploading application data
  • Updating records
  • Deleting records
  • Searching remote data
  • Fetching weather information
  • Communicating with payment or business APIs

Real-World Example

Suppose a shopping application needs to display products. The products may be stored on a backend server rather than inside the Flutter application.

Flutter Application
        ↓
GET /products
        ↓
Backend API
        ↓
Database
        ↓
JSON Response
        ↓
Flutter Application
        ↓
Product List

3. HTTP Requests and REST APIs

Most Flutter applications communicate with REST APIs using HTTP. A REST API usually exposes different endpoints for different operations.

HTTP Method Typical Purpose Example
GET Retrieve data Get a list of products
POST Create data Create a new user
PUT Update or replace data Update a complete user record
PATCH Partially update data Update only the user's name
DELETE Delete data Delete a product

REST API Example

GET     /api/users
POST    /api/users
GET     /api/users/10
PUT     /api/users/10
PATCH   /api/users/10
DELETE  /api/users/10

4. Adding the HTTP Package

The Flutter http package provides convenient methods for making HTTP requests.

Step 1: Add the Package

flutter pub add http

Step 2: Import the Package

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

After importing the package, you can use methods such as http.get(), http.post(), http.put(), http.patch(), and http.delete().


5. Internet Permission on Android

Android applications that access the internet need the appropriate internet permission in the Android manifest.

The permission is normally declared in:

android/app/src/main/AndroidManifest.xml

For applications that require internet access, the manifest can contain:

<uses-permission android:name="android.permission.INTERNET" />

Always verify the Android project configuration when an application cannot access the network.


6. Understanding Uri

An HTTP request needs a URL that identifies the server resource. In Dart, URLs are represented using the Uri class.

Using Uri.parse()

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

Using Uri.https()

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

Uri.https() is especially useful when constructing URLs programmatically because query parameters can be supplied separately.

final uri = Uri.https(
  'example.com',
  '/api/users',
  {
    'page': '1',
    'limit': '10',
  },
);

7. HTTP GET Request

A GET request is used to retrieve data from a server.

Basic GET Example

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

Future<http.Response> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/users'),
  );

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

  return response;
}

The http.get() method performs an asynchronous HTTP GET request and returns a Future containing an http.Response.

GET Request Flow

Flutter App
    ↓
http.get()
    ↓
GET /api/users
    ↓
Server
    ↓
HTTP Response
    ↓
response.body
    ↓
JSON Parsing
    ↓
Dart Model
    ↓
UI

8. Understanding Future

HTTP requests take time because the application must communicate with a remote server. Dart represents the result of an asynchronous operation using a Future.

Future<http.Response> fetchData() {
  return http.get(
    Uri.parse('https://example.com/api/data'),
  );
}

A Future represents a value that will become available later or an error if the asynchronous operation fails.

Future Lifecycle

Future Started
     ↓
Waiting for Network
     ↓
Server Responds
     ↓
Future Completes
     ↓
Result Available

9. Using async and await

The async and await keywords make asynchronous HTTP code easier to read and understand.

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

  print(response.body);
}

The async keyword marks a function as asynchronous, while await waits for the Future to complete before continuing.

Without await

final responseFuture = http.get(
  Uri.parse('https://example.com/api/data'),
);

With await

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

The second approach allows the code to work directly with the completed response inside the asynchronous function.


10. Understanding HTTP Response

An HTTP response contains information returned by the server.

Important properties include:

  • statusCode – HTTP status code returned by the server.
  • body – Response content, commonly JSON.
  • headers – Metadata returned by the server.
  • request – Information about the request when available.
final response = await http.get(
  Uri.parse('https://example.com/api/users'),
);

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

Response Structure

HTTP Response
├── statusCode
├── headers
├── body
└── request information

11. Checking Status Codes

Always check the response status code before assuming that a request was successful.

if (response.statusCode == 200) {
  print('Request successful');
} else {
  print('Request failed');
}
Status Code Meaning
200 OK / successful request
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error

Why Status Codes Matter

A request can complete at the network level but still represent an application-level error. Checking statusCode allows the application to distinguish successful and unsuccessful responses.


12. Handling JSON Responses

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

import 'dart:convert';

Suppose the server returns:

{
  "id": 1,
  "name": "Rahul",
  "email": "[email protected]"
}

You can decode it using jsonDecode().

final data = jsonDecode(response.body);

print(data['name']);

JSON Flow

HTTP Response
      ↓
response.body
      ↓
jsonDecode()
      ↓
Dart Map / List
      ↓
Dart Model
      ↓
Flutter UI

13. Creating a Dart Model

Instead of working with raw JSON throughout the application, it is better to convert API data into Dart model objects.

class User {
  final int id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });

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

Why Use Models?

  • Provides structured data.
  • Improves code readability.
  • Reduces repeated JSON access.
  • Makes application code easier to maintain.
  • Creates a clear boundary between API data and UI code.

14. Converting JSON to a Dart Object

Future<User> fetchUser() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/users/1'),
  );

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

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

This approach separates the raw API response from the rest of the application and makes the data easier to work with.


15. HTTP POST Request

A POST request is generally used to send data to a server and create a new resource.

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

Future<void> createUser() async {
  final response = await http.post(
    Uri.parse('https://example.com/api/users'),
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'name': 'Rahul',
      'email': '[email protected]',
    }),
  );

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

The jsonEncode() function converts Dart data into a JSON string before it is sent in the request body.

POST Request Flow

Dart Object / Map
       ↓
jsonEncode()
       ↓
JSON Request Body
       ↓
POST Request
       ↓
Server
       ↓
Response

16. HTTP Headers

HTTP headers provide additional information about a request or response.

Common headers include:

  • Content-Type
  • Accept
  • Authorization
  • User-Agent

Example

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

17. Sending JSON Headers

When sending JSON data, the server commonly needs to know that the request body contains JSON.

headers: {
  'Content-Type': 'application/json',
}

Complete Example

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

18. HTTP PUT Request

PUT is commonly used to update or replace an existing resource.

Future<void> updateUser() async {
  final response = await http.put(
    Uri.parse('https://example.com/api/users/1'),
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'name': 'Amit Sharma',
      'email': '[email protected]',
    }),
  );

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

19. HTTP PATCH Request

PATCH is commonly used when only part of an existing resource needs to be updated.

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

The exact support and expected behavior of PATCH depends on the backend API.

PUT vs PATCH

PUT PATCH
Commonly used for full replacement or update. Commonly used for partial updates.
May send the complete resource. Usually sends only changed fields.

20. HTTP DELETE Request

DELETE is used to remove a resource from a server.

Future<void> deleteUser(String id) async {
  final response = await http.delete(
    Uri.parse('https://example.com/api/users/$id'),
  );

  if (response.statusCode == 200 ||
      response.statusCode == 204) {
    print('User deleted');
  } else {
    print('Delete failed');
  }
}

21. Query Parameters

Query parameters are values added to the URL after a ?. They are commonly used for search, filtering, sorting, and pagination.

Using Uri.https()

final uri = Uri.https(
  'example.com',
  '/api/users',
  {
    'page': '1',
    'limit': '10',
    'search': 'rahul',
  },
);

final response = await http.get(uri);

Example URL

https://example.com/api/users?page=1&limit=10&search=rahul

Using Uri helps safely construct URLs and handle query parameter encoding.


22. Path Parameters

A path parameter is part of the URL path and is commonly used to identify a particular resource.

final userId = 10;

final response = await http.get(
  Uri.parse(
    'https://example.com/api/users/$userId',
  ),
);

Here, 10 identifies the requested user.

Path Parameter vs Query Parameter

Type Example Typical Purpose
Path Parameter /users/10 Identify a specific resource
Query Parameter /users?page=1 Filter, search, sort, or paginate

23. Authentication with HTTP Requests

Many APIs require authentication. A common approach is to send an authorization token in the HTTP headers.

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

Authentication Flow

User Login
    ↓
Authentication Server
    ↓
Access Token
    ↓
Flutter Application
    ↓
Authorization Header
    ↓
Protected API
    ↓
Authenticated Response

Important Security Rule

Do not hard-code private API keys, passwords, or sensitive credentials directly into a production Flutter application when they should remain secret. Client applications can be inspected, so sensitive secrets should generally be protected on the server side.


24. Handling HTTP Exceptions

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

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

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

Error Handling Flow

HTTP Request
     ↓
Try Operation
     ↓
Success? ── Yes ──→ Process Response
     │
     No
     ↓
Catch Exception
     ↓
Show Error / Retry

25. HTTP Request Timeout

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

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

You can handle timeout errors using TimeoutException or a general exception handler.

Example

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');
}

26. Loading, Success, Empty, and Error States

A production Flutter application should clearly handle different states while communicating with an API.

State Example UI
Loading CircularProgressIndicator
Success Display API data
Empty No data available message
Error Error message and retry button

Typical UI State Flow

Initial
  ↓
Loading
  ↓
Success → Display Data
  │
  ├── Empty → Display Empty State
  │
  └── Error → Display Error + Retry

27. Using FutureBuilder with HTTP Requests

FutureBuilder can be used to build UI based on the state of a Future.

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

  if (response.statusCode == 200) {
    return response.body;
  }

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

class MyPage extends StatelessWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchData(),
      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 Center(
            child: Text(snapshot.data!),
          );
        }

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

Note: For repeated rebuild scenarios, be careful about creating the Future directly inside build(). Store the Future when appropriate.


28. Avoid Making API Calls Directly Inside build()

Calling an HTTP method directly inside build() can cause repeated requests because Flutter may rebuild widgets multiple times.

Instead, initialize the Future once when appropriate.

late Future<String> dataFuture;

@override
void initState() {
  super.initState();
  dataFuture = fetchData();
}

The Future can then be passed to FutureBuilder without recreating the network request on every rebuild.


29. Creating an API Service Class

As an application becomes larger, HTTP requests should be separated from UI code.

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

  Future<http.Response> getUsers() async {
    return http.get(
      Uri.parse('$baseUrl/users'),
    );
  }

  Future<http.Response> createUser(
    String name,
    String email,
  ) async {
    return http.post(
      Uri.parse('$baseUrl/users'),
      headers: {
        'Content-Type': 'application/json',
      },
      body: jsonEncode({
        'name': name,
        'email': email,
      }),
    );
  }
}

Why Use an API Service?

  • Keeps networking logic separate from widgets.
  • Improves code organization.
  • Makes methods reusable.
  • Makes testing easier.
  • Allows common configuration to be centralized.

30. Recommended Project Structure

A larger Flutter application can separate API, models, repositories, and UI code.

lib/
├── models/
│   └── user.dart
├── services/
│   └── api_service.dart
├── repositories/
│   └── user_repository.dart
├── screens/
│   └── users_screen.dart
├── widgets/
│   └── user_card.dart
└── main.dart

This separation makes code easier to maintain, test, and reuse.

Responsibility of Each Layer

Layer Responsibility
Model Represents structured application data.
Service Communicates with external APIs.
Repository Provides an abstraction over data sources.
Screen Displays the application UI.
Widget Provides reusable UI components.

31. Repository Pattern

A repository can act as a layer between the UI or ViewModel and the API service.

class UserRepository {
  final ApiService apiService;

  UserRepository(this.apiService);

  Future<List<User>> getUsers() async {
    final response = await apiService.getUsers();

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

    final List<dynamic> data = jsonDecode(response.body);

    return data
        .map((item) => User.fromJson(item))
        .toList();
  }
}

Repository Flow

UI
 ↓
ViewModel / State Layer
 ↓
Repository
 ↓
ApiService
 ↓
HTTP API
 ↓
Server

32. HTTP Requests with MVVM

HTTP requests can be organized using an MVVM architecture, where the Model handles data structures, the View displays the UI, and the ViewModel manages application state and connects the UI with the data layer.

View
  ↓
ViewModel
  ↓
Repository / Model
  ↓
HTTP API
  ↓
Server

This separation helps keep networking code away from presentation code and makes the application easier to maintain.


33. Complete GET Request Example

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

class User {
  final int id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });

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

Future<User> fetchUser() async {
  final uri = Uri.parse(
    'https://example.com/api/users/1',
  );

  final response = await http.get(
    uri,
    headers: {
      'Accept': 'application/json',
    },
  );

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    return User.fromJson(data);
  }

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

GET Processing Flow

GET Request
    ↓
HTTP Response
    ↓
Check statusCode
    ↓
jsonDecode()
    ↓
User.fromJson()
    ↓
User Object
    ↓
UI

34. Complete POST Request Example

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

Future<void> createUser() async {
  final uri = Uri.parse(
    'https://example.com/api/users',
  );

  final response = await http.post(
    uri,
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: jsonEncode({
      'name': 'Neha',
      'email': '[email protected]',
    }),
  );

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

35. Sending Form Data

Not every API expects JSON. Some APIs may require form-encoded data or multipart requests. Always follow the API documentation for the expected content type and request format.

For example, URL-encoded data can be represented using appropriate request headers and encoded values.

Conceptual Flow

Flutter Form
    ↓
Form Values
    ↓
Encode According to API
    ↓
HTTP Request
    ↓
Backend

36. Sending File Data

Applications such as profile editors, document managers, and social media apps may need to upload files. The http package supports multipart requests through MultipartRequest.

final request = http.MultipartRequest(
  'POST',
  Uri.parse('https://example.com/api/upload'),
);

request.files.add(
  await http.MultipartFile.fromPath(
    'file',
    filePath,
  ),
);

final response = await request.send();

print(response.statusCode);

Multipart Upload Flow

Selected File
    ↓
MultipartFile
    ↓
MultipartRequest
    ↓
POST /upload
    ↓
Server
    ↓
Upload Response

37. Pagination

APIs often return large datasets in multiple pages rather than sending thousands of records at once.

Example URL

https://example.com/api/products?page=1&limit=20

Flutter Example

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

final response = await http.get(uri);

Pagination Flow

Page 1
 ↓
Load More
 ↓
Page 2
 ↓
Load More
 ↓
Page 3
 ↓
Continue Until No More Data

38. Search API Requests

Search functionality can send the search keyword to an API.

final uri = Uri.https(
  'example.com',
  '/api/products/search',
  {
    'query': 'laptop',
  },
);

final response = await http.get(uri);

The API can use the query value to search its available data and return matching records.


39. Pull-to-Refresh

Flutter applications often allow users to refresh API data using RefreshIndicator.

RefreshIndicator(
  onRefresh: () async {
    await fetchUsers();
  },
  child: ListView(
    children: const [
      ListTile(title: Text('User 1')),
      ListTile(title: Text('User 2')),
    ],
  ),
)

Pull-to-Refresh Flow

User Pulls Down
      ↓
RefreshIndicator
      ↓
API Request
      ↓
New Data
      ↓
Update UI

40. Retry Failed Requests

A retry mechanism can allow users to request the data again after a temporary network failure.

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

A retry button is especially useful when the original request fails because of a temporary connectivity problem.


41. API Caching

Caching means storing previously retrieved data so that it can sometimes be reused without making another network request.

Benefits can include:

  • Reduced network requests
  • Faster display of previously loaded data
  • Better user experience in some scenarios
  • Reduced server traffic

Caching strategy should be selected according to the application's data freshness requirements.

Basic Caching Concept

Request Data
    ↓
Check Cache
    ↓
Cached Data Available?
   / \
 Yes  No
  ↓    ↓
Use   API Request
Cache    ↓
  ↓    Save Cache
  └─────┬─────
        ↓
      Display

42. Parsing Large JSON Responses

Parsing a very large JSON document can require significant computation. For large responses, expensive JSON parsing can be moved to a separate Dart isolate when appropriate so that UI work is not unnecessarily blocked.

final response = await client.get(
  Uri.parse('https://example.com/api/photos'),
);

For large responses, consider separating network retrieval from expensive parsing and using an isolate when appropriate.

Conceptual Flow

Network Response
      ↓
Large JSON
      ↓
Background Isolate
      ↓
JSON Parsing
      ↓
Structured Data
      ↓
Main Isolate
      ↓
UI

43. Using http.Client

Instead of calling top-level methods directly, an application can use an http.Client.

final client = http.Client();

try {
  final response = await client.get(
    Uri.parse('https://example.com/api/users'),
  );

  print(response.body);
} finally {
  client.close();
}

Using a client can also make networking code easier to test and substitute with different implementations.

Why Use http.Client?

  • Provides an explicit HTTP client instance.
  • Can be injected into service classes.
  • Can make testing easier.
  • Allows the client lifecycle to be controlled.

44. Testing HTTP Requests

Networking code should be designed so that it can be tested without depending on a real production server.

A service that accepts an http.Client is easier to replace with a fake or mock client during testing.

class ApiService {
  final http.Client client;

  ApiService(this.client);

  Future<http.Response> getUsers() {
    return client.get(
      Uri.parse('https://example.com/api/users'),
    );
  }
}

Testing Architecture

Test
 ↓
Fake / Mock Client
 ↓
ApiService
 ↓
Application Logic

This avoids making unnecessary real network requests during unit tests.


45. Common HTTP Request Mistakes

  • Forgetting to add the http package.
  • Forgetting Android internet permission when required.
  • Not checking statusCode.
  • Trying to decode invalid JSON.
  • Making API requests repeatedly inside build().
  • Hard-coding sensitive credentials in the application.
  • Ignoring network exceptions.
  • Not displaying a loading state.
  • Not providing a retry option after failures.
  • Putting all API code directly inside UI widgets.
  • Not validating the API response structure.

Common Mistake Example

@override
Widget build(BuildContext context) {
  final future = fetchData();

  return FutureBuilder(
    future: future,
    builder: (context, snapshot) {
      // UI
    },
  );
}

Depending on the widget's rebuild behavior, creating the request this way can cause repeated network calls. Store the Future at an appropriate lifecycle point when necessary.


46. Best Practices for HTTP Requests

  1. Use the http package for straightforward HTTP communication.
  2. Use async and await for asynchronous operations.
  3. Use Uri to construct URLs safely.
  4. Check HTTP status codes.
  5. Handle exceptions and timeouts.
  6. Convert JSON into strongly structured Dart models.
  7. Keep API code separate from UI code.
  8. Use repositories or service classes in larger applications.
  9. Do not expose sensitive secrets in client-side code.
  10. Test networking code using injectable clients or mocks.
  11. Use pagination for large datasets.
  12. Consider caching when appropriate.
  13. Move expensive JSON parsing to an isolate when necessary.

47. HTTP Request Lifecycle

1. User performs an action
        ↓
2. Flutter creates the request
        ↓
3. Uri is created
        ↓
4. HTTP method is selected
        ↓
5. Headers/body are added
        ↓
6. Request is sent
        ↓
7. Server processes request
        ↓
8. Response is returned
        ↓
9. Status code is checked
        ↓
10. JSON is decoded
        ↓
11. JSON is converted to a model
        ↓
12. UI state is updated
        ↓
13. Data is displayed

Complete Architecture Flow

User
 ↓
Flutter UI
 ↓
State / ViewModel
 ↓
Repository
 ↓
API Service
 ↓
HTTP Client
 ↓
REST API
 ↓
Backend
 ↓
Database
 ↓
JSON Response
 ↓
Model
 ↓
State Update
 ↓
Flutter UI

48. Practical Example: User List App

A simple Flutter user-list application can follow this architecture:

Flutter UI
   ↓
UserViewModel
   ↓
UserRepository
   ↓
ApiService
   ↓
HTTP GET
   ↓
Backend API
   ↓
JSON Response
   ↓
User Model
   ↓
UI List

Example API Service

class ApiService {
  final http.Client client;

  ApiService(this.client);

  Future<List<User>> fetchUsers() async {
    final response = await client.get(
      Uri.parse('https://example.com/api/users'),
    );

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

    final List<dynamic> data = jsonDecode(response.body);

    return data
        .map((item) => User.fromJson(item))
        .toList();
  }
}

Practical UI Concept

Loading
   ↓
CircularProgressIndicator

Success
   ↓
ListView
   ↓
User Cards

Error
   ↓
Error Message
   ↓
Retry Button

49. HTTP Request vs HTTP Response

Request Response
Sent by Flutter app Sent by server
Contains URL Contains response data
Contains HTTP method Contains status code
Can contain headers Can contain headers
Can contain a body Can contain a body

Simple Comparison

Request
├── URL
├── Method
├── Headers
└── Body

Response
├── Status Code
├── Headers
└── Body

50. HTTP Methods Quick Revision

Method Purpose Flutter Method
GET Read data http.get()
POST Create/send data http.post()
PUT Update data http.put()
PATCH Partially update data http.patch()
DELETE Delete data http.delete()

51. Mini Project Exercise

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

Requirements

  • Add the http package.
  • Create a User model.
  • Create an API service.
  • Send a GET request.
  • Decode JSON.
  • Convert JSON into User objects.
  • Display users in a ListView.
  • Show a loading indicator while the request is running.
  • Show an error message when the request fails.
  • Add a retry button.

Suggested Project Flow

main.dart
   ↓
UsersScreen
   ↓
User State
   ↓
UserRepository
   ↓
ApiService
   ↓
GET /users
   ↓
JSON
   ↓
User.fromJson()
   ↓
List<User>
   ↓
ListView

52. Practice Questions

  1. What is an HTTP request?
  2. What is the difference between a request and a response?
  3. What is the purpose of the http package?
  4. What is the purpose of http.get()?
  5. When should you use POST?
  6. What is the difference between PUT and PATCH?
  7. What does DELETE do?
  8. What is an HTTP status code?
  9. Why should status codes be checked?
  10. What is JSON?
  11. What does jsonDecode() do?
  12. Why should API data be converted into Dart models?
  13. What are HTTP headers?
  14. How are authentication tokens commonly sent?
  15. Why are async and await used with HTTP requests?
  16. Why should API calls not normally be triggered directly from build()?
  17. What is an API service class?
  18. What is the repository pattern?
  19. Why is pagination useful?
  20. When might JSON parsing need to be moved to another isolate?

53. Interview Questions

Q1. What package is commonly used for HTTP requests in Flutter?

The http package is a common and straightforward option for making HTTP requests in Flutter.

Q2. What does http.get() return?

It returns a Future containing an http.Response.

Q3. Why is async used with HTTP requests?

HTTP communication is asynchronous. The async keyword allows a function to perform asynchronous operations and return a Future.

Q4. Why is await used?

await waits for a Future to complete before continuing with the next statements in the asynchronous function.

Q5. Why should you check statusCode?

The status code indicates how the server handled the request. Checking it allows the application to distinguish successful responses from error responses.

Q6. Why use a model class?

A model class converts raw API data into a structured Dart object, making application code easier to understand and maintain.

Q7. What is the difference between PUT and PATCH?

PUT is commonly used for updating or replacing a resource, while PATCH is commonly used for partial updates. The exact semantics depend on the API design.

Q8. Why should API calls be separated from UI code?

Separating networking logic improves organization, reusability, maintainability, and testability.

Q9. Why should sensitive API secrets not be stored directly in a Flutter application?

Client applications can be inspected. Secrets that must remain private should generally be protected and used on a trusted server-side environment.

Q10. Why is pagination useful?

Pagination allows an API to return large datasets in smaller portions, reducing the amount of data transferred and processed at one time.


54. Official Flutter Networking Resources


55. Learn Flutter with JustAcademy

For structured Flutter training and practical learning, explore the following resources:

JustAcademy Flutter Training Course

Register for Flutter Course Demo


56. Quick Revision

Concept Key Point
HTTP Protocol used for communication between clients and servers.
http Package Provides methods for making HTTP requests.
GET Retrieve data.
POST Create or submit data.
PUT Update or replace data.
PATCH Partially update data.
DELETE Remove data.
Uri Represents and constructs URLs.
Future Represents a value available asynchronously.
async/await Used to write readable asynchronous code.
JSON Common format for API data exchange.
Model Converts API data into structured Dart objects.
Headers Provide metadata and authentication information.
Repository Provides an abstraction over data sources.
Pagination Loads large datasets in smaller portions.
http.Client Provides an explicit HTTP client that can be injected and tested.

57. Learning Outcome

After completing this topic, you should be able to:

  • Understand HTTP communication in Flutter.
  • Explain the difference between HTTP requests and responses.
  • Add and use the http package.
  • Create GET, POST, PUT, PATCH, and DELETE requests.
  • Construct URLs using Uri.
  • Use query parameters and path parameters.
  • Send headers and JSON request bodies.
  • Handle authentication headers.
  • Understand Futures and asynchronous HTTP operations.
  • Decode JSON responses.
  • Create Dart model classes.
  • Handle loading, success, empty, and error states.
  • Use FutureBuilder appropriately.
  • Separate API code from UI code.
  • Use service and repository layers.
  • Understand basic MVVM networking architecture.
  • Handle pagination, caching, retries, and file uploads.
  • Design networking code that can be tested.

58. Recommended HTTP Development Process

1. Understand the API documentation
        ↓
2. Identify endpoint and HTTP method
        ↓
3. Construct Uri
        ↓
4. Add required headers
        ↓
5. Add request body if required
        ↓
6. Send asynchronous request
        ↓
7. Apply timeout/error handling
        ↓
8. Check statusCode
        ↓
9. Decode JSON
        ↓
10. Convert JSON to models
        ↓
11. Update application state
        ↓
12. Display loading/success/error UI
        ↓
13. Test the networking layer

59. Summary

HTTP requests allow Flutter applications to communicate with remote APIs and backend services. The http package provides convenient methods such as get(), post(), put(), patch(), and delete().

A typical API workflow involves creating a URI, selecting an HTTP method, adding headers and a request body when required, sending the asynchronous request, waiting for the response, checking the status code, decoding JSON, converting the response into Dart models, and updating the UI.

For production applications, networking code should be separated from UI code, errors and loading states should be handled properly, authentication should be implemented securely, and larger applications can use an appropriate architecture such as service/repository layers or MVVM.

Core concept to remember:

Request
  ↓
Server
  ↓
Response
  ↓
Status Check
  ↓
JSON Decode
  ↓
Dart Model
  ↓
Application State
  ↓
Flutter UI
Previous Next
whatsapp