Handling Different HTTP Request Methods (GET, POST, PUT, DELETE) and Status Codes in Flutter

When building Flutter applications that interact with APIs, understanding and handling different HTTP request methods (GET, POST, PUT, DELETE) and HTTP status codes is crucial. Each method serves a specific purpose, and correctly interpreting status codes ensures your app behaves as expected. This guide will walk you through implementing these concepts with detailed code examples.

Understanding HTTP Request Methods

HTTP defines several request methods to perform different operations on a resource. Here are the most common ones:

  • GET: Retrieves data from the server.
  • POST: Sends data to the server to create a new resource.
  • PUT: Updates an existing resource on the server.
  • DELETE: Deletes a specific resource on the server.

Setting Up Your Flutter Environment

First, you need to set up your Flutter environment and add the necessary dependencies. Make sure you have Flutter installed. Then, add the http package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0 # Use the latest version

After adding the dependency, run flutter pub get to install it.

Performing GET Requests in Flutter

A GET request is used to retrieve data from the server. Here’s how you can implement it:

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

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

  try {
    final response = await http.get(url);

    if (response.statusCode == 200) {
      // If the server returns an OK response, parse the JSON.
      final data = jsonDecode(response.body);
      print(data);
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      print('Failed to load data. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage in a Flutter Widget:

import 'package:flutter/material.dart';

class FetchDataExample extends StatefulWidget {
  @override
  _FetchDataExampleState createState() => _FetchDataExampleState();
}

class _FetchDataExampleState extends State<FetchDataExample> {
  String data = 'Loading...';

  @override
  void initState() {
    super.initState();
    fetchData().then((value) {
      setState(() {
        data = 'Data fetched successfully';
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GET Request Example'),
      ),
      body: Center(
        child: Text(data),
      ),
    );
  }

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

    try {
      final response = await http.get(url);

      if (response.statusCode == 200) {
        // If the server returns an OK response, parse the JSON.
        final decodedData = jsonDecode(response.body);
        setState(() {
          data = 'Data: ${decodedData.toString()}';
        });
        print(decodedData);
      } else {
        // If the server did not return a 200 OK response,
        // then throw an exception.
        setState(() {
          data = 'Failed to load data. Status code: ${response.statusCode}';
        });
        print('Failed to load data. Status code: ${response.statusCode}');
      }
    } catch (e) {
      setState(() {
        data = 'Error: ${e.toString()}';
      });
      print('Error: $e');
    }
  }
}

Performing POST Requests in Flutter

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

Future<void> postData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final headers = {'Content-Type': 'application/json'};
  final body = jsonEncode({
    'title': 'Flutter Post',
    'body': 'This is a POST request example',
    'userId': 1,
  });

  try {
    final response = await http.post(url, headers: headers, body: body);

    if (response.statusCode == 201) {
      // If the server returns a 201 Created response, parse the JSON.
      final data = jsonDecode(response.body);
      print('Posted data: $data');
    } else {
      // If the server did not return a 201 Created response,
      // then throw an exception.
      print('Failed to post data. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage in a Flutter Widget:

class PostDataExample extends StatefulWidget {
  @override
  _PostDataExampleState createState() => _PostDataExampleState();
}

class _PostDataExampleState extends State<PostDataExample> {
  String message = 'Click the button to post data';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('POST Request Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(message),
            ElevatedButton(
              onPressed: () {
                postData().then((_) {
                  setState(() {
                    message = 'Data posted successfully!';
                  });
                });
              },
              child: Text('Post Data'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> postData() async {
    final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final headers = {'Content-Type': 'application/json'};
    final body = jsonEncode({
      'title': 'Flutter Post',
      'body': 'This is a POST request example',
      'userId': 1,
    });

    try {
      final response = await http.post(url, headers: headers, body: body);

      if (response.statusCode == 201) {
        // If the server returns a 201 Created response, parse the JSON.
        final data = jsonDecode(response.body);
        print('Posted data: $data');
        setState(() {
          message = 'Data posted successfully!nResponse: ${data.toString()}';
        });
      } else {
        // If the server did not return a 201 Created response,
        // then throw an exception.
        print('Failed to post data. Status code: ${response.statusCode}');
        setState(() {
          message = 'Failed to post data. Status code: ${response.statusCode}';
        });
      }
    } catch (e) {
      print('Error: $e');
      setState(() {
        message = 'Error: ${e.toString()}';
      });
    }
  }
}

Performing PUT Requests in Flutter

A PUT request is used to update an existing resource on the server.

Future<void> putData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  final headers = {'Content-Type': 'application/json'};
  final body = jsonEncode({
    'id': 1,
    'title': 'Updated Flutter Post',
    'body': 'This is an updated PUT request example',
    'userId': 1,
  });

  try {
    final response = await http.put(url, headers: headers, body: body);

    if (response.statusCode == 200) {
      // If the server returns a 200 OK response, parse the JSON.
      final data = jsonDecode(response.body);
      print('Updated data: $data');
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      print('Failed to update data. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage in a Flutter Widget:

class PutDataExample extends StatefulWidget {
  @override
  _PutDataExampleState createState() => _PutDataExampleState();
}

class _PutDataExampleState extends State<PutDataExample> {
  String message = 'Click the button to update data';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PUT Request Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(message),
            ElevatedButton(
              onPressed: () {
                putData().then((_) {
                  setState(() {
                    message = 'Data updated successfully!';
                  });
                });
              },
              child: Text('Update Data'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> putData() async {
    final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
    final headers = {'Content-Type': 'application/json'};
    final body = jsonEncode({
      'id': 1,
      'title': 'Updated Flutter Post',
      'body': 'This is an updated PUT request example',
      'userId': 1,
    });

    try {
      final response = await http.put(url, headers: headers, body: body);

      if (response.statusCode == 200) {
        // If the server returns a 200 OK response, parse the JSON.
        final data = jsonDecode(response.body);
        print('Updated data: $data');
        setState(() {
          message = 'Data updated successfully!nResponse: ${data.toString()}';
        });
      } else {
        // If the server did not return a 200 OK response,
        // then throw an exception.
        print('Failed to update data. Status code: ${response.statusCode}');
        setState(() {
          message = 'Failed to update data. Status code: ${response.statusCode}';
        });
      }
    } catch (e) {
      print('Error: $e');
      setState(() {
        message = 'Error: ${e.toString()}';
      });
    }
  }
}

Performing DELETE Requests in Flutter

A DELETE request is used to delete a specific resource on the server.

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

  try {
    final response = await http.delete(url);

    if (response.statusCode == 200) {
      // If the server returns a 200 OK response.
      print('Data deleted successfully');
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      print('Failed to delete data. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Usage in a Flutter Widget:

class DeleteDataExample extends StatefulWidget {
  @override
  _DeleteDataExampleState createState() => _DeleteDataExampleState();
}

class _DeleteDataExampleState extends State<DeleteDataExample> {
  String message = 'Click the button to delete data';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DELETE Request Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(message),
            ElevatedButton(
              onPressed: () {
                deleteData().then((_) {
                  setState(() {
                    message = 'Data deleted successfully!';
                  });
                });
              },
              child: Text('Delete Data'),
            ),
          ],
        ),
      ),
    );
  }

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

    try {
      final response = await http.delete(url);

      if (response.statusCode == 200) {
        // If the server returns a 200 OK response.
        print('Data deleted successfully');
        setState(() {
          message = 'Data deleted successfully!';
        });
      } else {
        // If the server did not return a 200 OK response,
        // then throw an exception.
        print('Failed to delete data. Status code: ${response.statusCode}');
        setState(() {
          message = 'Failed to delete data. Status code: ${response.statusCode}';
        });
      }
    } catch (e) {
      print('Error: $e');
      setState(() {
        message = 'Error: ${e.toString()}';
      });
    }
  }
}

Handling HTTP Status Codes

HTTP status codes are three-digit numbers that indicate the outcome of an HTTP request. Properly handling these status codes is essential for providing a good user experience. Here are some common status codes and how to handle them:

  • 200 OK: The request was successful.
  • 201 Created: The request was successful, and a new resource was created (e.g., after a POST request).
  • 204 No Content: The request was successful, but there is no content to return (e.g., after a DELETE request).
  • 400 Bad Request: The server cannot process the request due to a client error (e.g., invalid data).
  • 401 Unauthorized: The request requires authentication.
  • 403 Forbidden: The server understood the request, but refuses to authorize it.
  • 404 Not Found: The requested resource was not found.
  • 500 Internal Server Error: The server encountered an unexpected condition.
  • 503 Service Unavailable: The server is not ready to handle the request (e.g., due to maintenance).

Example of Handling Status Codes:

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

  try {
    final response = await http.get(url);

    switch (response.statusCode) {
      case 200:
        // Success
        final data = jsonDecode(response.body);
        print(data);
        break;
      case 404:
        // Not Found
        print('Resource not found');
        break;
      case 500:
        // Internal Server Error
        print('Server error');
        break;
      default:
        // Handle other status codes
        print('Unexpected status code: ${response.statusCode}');
        break;
    }
  } catch (e) {
    print('Error: $e');
  }
}

Conclusion

Handling different HTTP request methods and status codes correctly is fundamental to building robust Flutter applications that interact with APIs. By using the examples provided, you can ensure your app efficiently retrieves, creates, updates, and deletes data, while also gracefully handling different server responses. Proper error handling and status code management are crucial for a seamless user experience.