Sending GET and POST Requests in Flutter
GET and POST are two of the most commonly used HTTP methods in Flutter applications. A GET request is generally used to retrieve data from a server, while a POST request is generally used to send data to a server to create a new resource or submit information.
Flutter provides the http package as a simple way to communicate with REST APIs and web services. The official Flutter networking documentation demonstrates using http.get() for fetching data and http.post() for sending data. :contentReference[oaicite:0]{index=0}
1. What Are GET and POST Requests?
HTTP methods describe the action that a client wants to perform on a server.
| Method |
Main Purpose |
Example |
| GET |
Retrieve existing data |
Get a list of products |
| POST |
Send or create new data |
Create a new user |
2. GET Request in Flutter
A GET request is used when the Flutter application needs to retrieve information from an API server.
Examples of GET Requests
- Searching for data from an API
3. POST Request in Flutter
A POST request is commonly used to send information to a server. For example, a Flutter application can send registration information to a backend server to create a new account.
Examples of POST Requests
- Uploading structured JSON data
4. Installing the HTTP Package
Before sending GET or POST requests, add the http package to the Flutter project.
flutter pub add http
Then import the package:
import 'package:http/http.dart' as http;
Flutter's networking cookbook uses the http package for both fetching and sending data. :contentReference[oaicite:1]{index=1}
5. Android Internet Permission
Android applications that access the internet should declare the Internet permission in the Android manifest.
6. Basic GET Request
The http.get() method sends a GET request and returns a Future.
import 'package:http/http.dart' as http;
Future fetchAlbum() {
return http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
);
}
Because network communication is asynchronous, the result is returned through a Future. :contentReference[oaicite:2]{index=2}
7. GET Request Using async and await
In practical Flutter applications, async and await make asynchronous HTTP code easier to read.
Future fetchData() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
);
print(response.statusCode);
print(response.body);
}
Understanding the Code
async marks the function as asynchronous.
await waits for the HTTP request to complete.
http.get() sends the GET request.
response.statusCode contains the HTTP status code.
response.body contains the response data.
8. Checking GET Response Status
Always check the HTTP status code before processing the response as successful.
Future fetchAlbum() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
);
if (response.statusCode == 200) {
print('Request successful');
print(response.body);
} else {
print('Request failed');
}
}
9. Common GET Response Status Codes
| Status Code |
Meaning |
| 200 |
Request successful |
| 400 |
Bad request |
| 401 |
Authentication required or failed |
| 403 |
Access forbidden |
| 404 |
Resource not found |
| 500 |
Server error |
| 503 |
Service unavailable |
10. Reading JSON from a GET Request
Most REST APIs return JSON. Dart's dart:convert library provides jsonDecode() for converting JSON text into Dart data.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future fetchAlbum() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print(data['id']);
print(data['title']);
}
}
11. Creating a Model for GET Response
Using a model class makes API data easier to manage and provides stronger type safety.
class Album {
final int userId;
final int id;
final String title;
const Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map json) {
return Album(
userId: json['userId'] as int,
id: json['id'] as int,
title: json['title'] as String,
);
}
}
12. GET Request Returning a Model
import 'dart:convert';
import 'package:http/http.dart' as http;
Future fetchAlbum() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body)
as Map;
return Album.fromJson(data);
}
throw Exception('Failed to load album');
}
Flutter's official recipe follows this general pattern: make the GET request, check for a successful response, decode the JSON body, and convert it into a Dart object. :contentReference[oaicite:3]{index=3}
13. GET Request with Headers
Headers provide additional information to the server. For example, the Accept header can indicate that the client expects JSON.
final response = await http.get(
Uri.parse('https://example.com/api/products'),
headers: {
'Accept': 'application/json',
},
);
14. GET Request with Authorization
Protected APIs may require authentication information in request headers. A common approach is an Authorization header containing an access token.
final response = await http.get(
Uri.parse('https://example.com/api/profile'),
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Accept': 'application/json',
},
);
The exact authentication format depends on the API. Flutter's documentation demonstrates adding authorization information through request headers. :contentReference[oaicite:4]{index=4}
15. GET Request with Query Parameters
Query parameters allow an application to send additional filtering or search information in the URL.
final uri = Uri.https(
'example.com',
'/api/products',
{
'category': 'mobile',
'page': '1',
'limit': '20',
},
);
final response = await http.get(uri);
Example URL
https://example.com/api/products?category=mobile&page=1&limit=20
16. GET Request with Path Parameters
Path parameters are commonly used when requesting a specific resource.
final userId = 10;
final response = await http.get(
Uri.parse('https://example.com/api/users/$userId'),
);
17. GET Request for a List
An API may return an array of objects rather than a single object.
Future> fetchAlbums() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
);
if (response.statusCode != 200) {
throw Exception('Failed to load albums');
}
final List data = jsonDecode(response.body);
return data
.map((item) => Album.fromJson(
item as Map,
))
.toList();
}
18. Displaying GET Data in ListView
FutureBuilder>(
future: fetchAlbums(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
}
final albums = snapshot.data ?? [];
if (albums.isEmpty) {
return const Center(
child: Text('No albums found'),
);
}
return ListView.builder(
itemCount: albums.length,
itemBuilder: (context, index) {
final album = albums[index];
return ListTile(
title: Text(album.title),
subtitle: Text('ID: ${album.id}'),
);
},
);
},
)
19. Why Avoid API Calls Inside build()?
An API request should generally not be created directly inside the build() method. Flutter can call build() many times, which could result in repeated network requests.
Incorrect
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchAlbum(),
builder: (context, snapshot) {
return const SizedBox();
},
);
}
Better Approach
late Future futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
}
if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
Flutter's official fetch-data recipe recommends initiating the request from initState() or didChangeDependencies() rather than directly from build(). :contentReference[oaicite:5]{index=5}
20. What Is a POST Request?
A POST request sends data from the Flutter application to a server. The server can use that data to create a new resource or perform an operation.
Example
Flutter Form
|
| POST
v
Backend API
|
| Save Data
v
Database
21. Basic POST Request
The http.post() method can send data to a server.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future createAlbum(String title) {
return http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': title,
}),
);
}
Flutter's official networking recipe uses http.post(), a JSON-encoded body, and a Content-Type: application/json header for sending JSON data. :contentReference[oaicite:6]{index=6}
22. POST Request Using async and await
Future createAlbum() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': 'Flutter Course',
}),
);
print(response.statusCode);
print(response.body);
}
23. Understanding the POST Request
http.post() sends the request.
Uri.parse() creates the request URL.
headers provides request metadata.
Content-Type tells the server that the body contains JSON.
jsonEncode() converts Dart data into a JSON string.
body contains the data being sent.
- The returned
Future contains the server response.
24. Sending Multiple Fields with POST
Future registerUser() async {
final userData = {
'name': 'Rahul',
'email': '[email protected]',
'password': 'examplePassword',
'phone': '9876543210',
};
final response = await http.post(
Uri.parse('https://example.com/api/register'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode(userData),
);
if (response.statusCode == 201) {
print('User registered successfully');
} else {
print('Registration failed');
}
}
25. POST Request and JSON Response
Many APIs return information about the resource created by the POST request. The response can be decoded using jsonDecode().
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',
'body': 'Learning POST requests',
'userId': 1,
}),
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
print('Created ID: ${data['id']}');
print('Title: ${data['title']}');
} else {
print('Failed to create post');
}
}
26. Understanding the 201 Created Status
A successful resource-creation request commonly returns HTTP status 201 Created. The exact status code depends on the API design.
if (response.statusCode == 201) {
print('Resource created successfully');
} else {
print('Failed to create resource');
}
Flutter's official POST example checks for a 201 response before converting the response body into its model object. :contentReference[oaicite:7]{index=7}
27. Creating a POST Model
class Album {
final int id;
final String title;
const Album({
required this.id,
required this.title,
});
factory Album.fromJson(Map json) {
return Album(
id: json['id'] as int,
title: json['title'] as String,
);
}
}
28. POST Request Returning a Model
Future createAlbum(String title) async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': title,
}),
);
if (response.statusCode == 201) {
return Album.fromJson(
jsonDecode(response.body) as Map,
);
}
throw Exception('Failed to create album');
}
29. Sending TextField Data Using POST
POST requests are frequently used with Flutter forms. A TextEditingController can read user input and the value can then be sent to the API.
final TextEditingController titleController =
TextEditingController();
Future submitTitle() async {
final title = titleController.text.trim();
if (title.isEmpty) {
return;
}
final response = await http.post(
Uri.parse('https://example.com/api/albums'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'title': title,
}),
);
if (response.statusCode == 201) {
print('Data submitted');
}
}
30. Complete GET Example
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Album {
final int id;
final String title;
const Album({
required this.id,
required this.title,
});
factory Album.fromJson(Map json) {
return Album(
id: json['id'] as int,
title: json['title'] as String,
);
}
}
Future fetchAlbum() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
return Album.fromJson(
jsonDecode(response.body) as Map,
);
}
throw Exception('Failed to load album');
}
class AlbumScreen extends StatefulWidget {
const AlbumScreen({super.key});
@override
State createState() => _AlbumScreenState();
}
class _AlbumScreenState extends State {
late Future albumFuture;
@override
void initState() {
super.initState();
albumFuture = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GET Request'),
),
body: FutureBuilder(
future: albumFuture,
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 available'),
);
}
return Center(
child: Text(
snapshot.data!.title,
style: const TextStyle(fontSize: 24),
),
);
},
),
);
}
}
31. Complete POST Example
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Album {
final int id;
final String title;
const Album({
required this.id,
required this.title,
});
factory Album.fromJson(Map json) {
return Album(
id: json['id'] as int,
title: json['title'] as String,
);
}
}
Future createAlbum(String title) async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': title,
}),
);
if (response.statusCode == 201) {
return Album.fromJson(
jsonDecode(response.body) as Map,
);
}
throw Exception('Failed to create album');
}
class CreateAlbumScreen extends StatefulWidget {
const CreateAlbumScreen({super.key});
@override
State createState() =>
_CreateAlbumScreenState();
}
class _CreateAlbumScreenState extends State {
final TextEditingController titleController =
TextEditingController();
Future? albumFuture;
@override
void dispose() {
titleController.dispose();
super.dispose();
}
void submit() {
final title = titleController.text.trim();
if (title.isEmpty) {
return;
}
setState(() {
albumFuture = createAlbum(title);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('POST Request'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: 'Album Title',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: submit,
child: const Text('Create Album'),
),
const SizedBox(height: 24),
if (albumFuture != null)
FutureBuilder(
future: albumFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}',
);
}
if (snapshot.hasData) {
return Text(
'Created: ${snapshot.data!.title}',
);
}
return const SizedBox();
},
),
],
),
),
);
}
}
32. GET vs POST
| Feature |
GET |
POST |
| Purpose |
Retrieve data |
Send/create data |
| Typical data location |
URL/query parameters |
Request body |
| Common use |
Fetching products |
Creating users |
| JSON body |
Usually not used for basic GET APIs |
Commonly used |
| Common success code |
200 |
201 for resource creation |
| Flutter method |
http.get() |
http.post() |
33. Handling GET Errors
Future fetchAlbum() async {
try {
final response = await http
.get(
Uri.parse(
'https://jsonplaceholder.typicode.com/albums/1',
),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
return Album.fromJson(
jsonDecode(response.body)
as Map,
);
}
if (response.statusCode == 404) {
throw Exception('Album not found');
}
if (response.statusCode >= 500) {
throw Exception('Server error');
}
throw Exception(
'GET request failed: ${response.statusCode}',
);
} catch (e) {
throw Exception('Unable to fetch album: $e');
}
}
34. Handling POST Errors
Future createAlbum(String title) async {
try {
final response = await http
.post(
Uri.parse(
'https://example.com/api/albums',
),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'title': title,
}),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode == 201) {
return Album.fromJson(
jsonDecode(response.body)
as Map,
);
}
if (response.statusCode == 400) {
throw Exception('Invalid album data');
}
if (response.statusCode == 401) {
throw Exception('Authentication required');
}
throw Exception(
'POST request failed: ${response.statusCode}',
);
} catch (e) {
throw Exception('Unable to create album: $e');
}
}
35. Loading, Success, Empty, and Error States
GET and POST screens should provide appropriate feedback to the user while network operations are running.
| State |
Example |
| Loading |
Show progress indicator or disabled submit button |
| Success |
Display returned data or success message |
| Empty |
Show a message when a GET request returns no items |
| Error |
Show a useful error message and retry option |
36. Preventing Duplicate POST Requests
Users may accidentally press a submit button multiple times. Disable the button while the request is running.
bool isSubmitting = false;
Future submit() async {
if (isSubmitting) {
return;
}
setState(() {
isSubmitting = true;
});
try {
await createAlbum('Flutter Album');
} finally {
if (mounted) {
setState(() {
isSubmitting = false;
});
}
}
}
37. POST Request with Form Validation
final formKey = GlobalKey();
Form(
key: formKey,
child: Column(
children: [
TextFormField(
controller: titleController,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter a title';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
submit();
}
},
child: const Text('Submit'),
),
],
),
)
38. GET and POST Through a Service Class
For larger applications, networking code can be separated from the widgets.
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
static const String baseUrl =
'https://example.com/api';
Future> getAlbums() async {
final response = await http.get(
Uri.parse('$baseUrl/albums'),
headers: {
'Accept': 'application/json',
},
);
if (response.statusCode != 200) {
throw Exception('Failed to fetch albums');
}
final List data =
jsonDecode(response.body);
return data
.map(
(item) => Album.fromJson(
item as Map,
),
)
.toList();
}
Future createAlbum(String title) async {
final response = await http.post(
Uri.parse('$baseUrl/albums'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'title': title,
}),
);
if (response.statusCode != 201) {
throw Exception('Failed to create album');
}
return Album.fromJson(
jsonDecode(response.body)
as Map,
);
}
}
39. Recommended Architecture
Flutter UI
|
v
ViewModel / Controller
|
v
Repository / Service
|
v
http.get() / http.post()
|
v
REST API
|
v
Database
Benefits
- Better separation of responsibilities
- Less networking logic inside UI widgets
40. GET and POST Request Flow
GET Flow
User opens screen
↓
Flutter sends GET request
↓
Server receives request
↓
Server retrieves data
↓
Server returns JSON
↓
Flutter checks status code
↓
Flutter decodes JSON
↓
Model object created
↓
UI displays data
POST Flow
User enters data
↓
Flutter validates input
↓
Flutter encodes data as JSON
↓
Flutter sends POST request
↓
Server validates data
↓
Server creates resource
↓
Server returns response
↓
Flutter checks status code
↓
Flutter decodes response
↓
UI displays success/error state
41. Common Mistakes
- Forgetting to add the
http package.
- Forgetting Android internet permission when required.
- Not using
await correctly.
- Not checking the HTTP status code.
- Forgetting
jsonEncode() when sending JSON.
- Forgetting the
Content-Type header when the API expects JSON.
- Assuming every response contains valid JSON.
- Calling GET requests repeatedly from
build().
- Allowing users to submit a POST request multiple times accidentally.
- Not handling network exceptions.
- Not showing loading or error states.
- Putting all API code directly inside UI widgets.
- Hard-coding sensitive production credentials in the application.
42. Best Practices
- Use
http.get() when retrieving data.
- Use
http.post() when sending or creating data.
- Use
async and await for asynchronous operations.
- Always validate the response status code.
- Use model classes for structured API data.
- Use
jsonDecode() for reading JSON responses.
- Use
jsonEncode() for sending JSON request bodies.
- Set the correct
Content-Type header when required.
- Use authentication headers for protected endpoints.
- Use timeouts for network requests where appropriate.
- Separate networking code from the UI.
- Handle loading, success, empty, and error states.
- Prevent duplicate form submissions.
- Use an
http.Client when dependency injection and testing are useful.
- For very large JSON responses, consider parsing expensive data in a separate isolate.
43. Testing GET and POST Requests
Network-dependent code should be designed so that the HTTP client can be replaced with a test or mock implementation when necessary.
Future fetchAlbum(http.Client client) {
return client.get(
Uri.parse(
'https://jsonplaceholder.typicode.com/albums/1',
),
);
}
Passing an http.Client into the function makes the networking code easier to test. Flutter's background-parsing recipe uses this approach. :contentReference[oaicite:8]{index=8}
44. GET and POST Security Considerations
- Use HTTPS endpoints in production.
- Do not expose private API keys unnecessarily in client-side source code.
- Use secure authentication mechanisms provided by the backend.
- Validate user input before sending it.
- Do not trust client-side validation alone; validate data on the server as well.
- Handle expired authentication tokens properly.
- Do not display sensitive server errors directly to users.
45. Practice Project: User Management App
Create a Flutter application that demonstrates both GET and POST requests.
GET Requirements
- Fetch a list of users from an API.
- Display users using
ListView.builder.
- Show a loading indicator while data is loading.
- Show an error message if the request fails.
- Show an empty-state message if no users are returned.
POST Requirements
- Create a registration form.
- Add name, email, and password fields.
- Validate the fields.
- Send the form data using a POST request.
- Show a loading state during submission.
- Display the API response after submission.
- Prevent duplicate submissions.
46. Interview Questions
- What is an HTTP GET request?
- What is an HTTP POST request?
- What is the difference between GET and POST?
- How do you send a GET request in Flutter?
- How do you send a POST request in Flutter?
- What is the
http package?
- Why does
http.get() return a Future?
- Why is
async and await used with HTTP requests?
- How do you decode a JSON response?
- What is
jsonEncode()?
- Why is the
Content-Type header important for JSON POST requests?
- What does HTTP status code 200 mean?
- What does HTTP status code 201 mean?
- How do you handle an HTTP 404 error?
- How do you handle network exceptions?
- Why should API calls not normally be placed directly inside
build()?
- How can a GET response be converted into a Dart model?
- How can form data be submitted using POST?
- How do you add authentication information to an HTTP request?
- How can GET and POST networking code be separated from the UI?
47. Quick Revision
| Concept |
Important Point |
| GET |
Used to retrieve data |
| POST |
Used to send/create data |
http.get() |
Sends a GET request |
http.post() |
Sends a POST request |
Future |
Represents an asynchronous result |
jsonDecode() |
Converts JSON text to Dart data |
jsonEncode() |
Converts Dart data to JSON text |
statusCode |
Indicates the result of the request |
Content-Type |
Describes the request body format |
Authorization |
Can carry authentication credentials or tokens |
FutureBuilder |
Builds UI from asynchronous state |
| Model Class |
Represents structured API data |
48. Official Flutter Resources
49. Learn Flutter with JustAcademy
For structured Flutter development training, visit the JustAcademy Flutter Training Course.
To register for a Flutter course demonstration, visit the JustAcademy Flutter Course Demo Registration page.
Conclusion
GET and POST requests are fundamental for building Flutter applications that communicate with backend APIs. GET requests are primarily used to retrieve information, while POST requests are commonly used to submit or create information. Using the http package together with async, await, JSON encoding and decoding, model classes, status-code validation, error handling, and proper UI states allows developers to build reliable API-based Flutter applications.
The basic flow can be remembered as:
GET:
Flutter UI → http.get() → API → JSON Response → Model → UI
POST:
Flutter Form → Validate → jsonEncode() → http.post() → API → Response → UI