Handling Different HTTP Request Methods and Status Codes in Flutter

In Flutter development, interacting with RESTful APIs is a common task. Properly handling different HTTP request methods (e.g., GET, POST, PUT, DELETE) and status codes is crucial for building robust and reliable applications. This article will guide you through how to handle various HTTP request methods and status codes in Flutter.

Understanding HTTP Request Methods and Status Codes

Before diving into the code, let’s briefly discuss HTTP methods and status codes.

HTTP Request Methods

  • GET: Retrieves data from a specified resource.
  • POST: Submits data to be processed to a specified resource.
  • PUT: Updates a specified resource.
  • DELETE: Deletes a specified resource.
  • PATCH: Partially modifies a resource.

HTTP Status Codes

HTTP status codes indicate whether a specific HTTP request has been successfully completed. Here are some common status code ranges:

  • 1xx (Informational): Request received, continuing process.
  • 2xx (Successful): The request was successfully received, understood, and accepted.
  • 3xx (Redirection): Further action needs to be taken in order to complete the request.
  • 4xx (Client Error): The request contains bad syntax or cannot be fulfilled.
  • 5xx (Server Error): The server failed to fulfill an apparently valid request.

Some common specific status codes include:

  • 200 OK: Standard response for successful HTTP requests.
  • 201 Created: Request has been fulfilled, resulting in the creation of a new resource.
  • 204 No Content: The server successfully processed the request but is not returning any content.
  • 400 Bad Request: The server could not understand the request due to invalid syntax.
  • 401 Unauthorized: Authentication is required and has failed or has not yet been provided.
  • 403 Forbidden: The server understood the request, but is refusing to fulfill it.
  • 404 Not Found: The server has not found anything matching the Request-URI.
  • 500 Internal Server Error: A generic error message, given when no more specific message is suitable.

Setting Up Your Flutter Project

First, make sure you have Flutter installed. Create a new Flutter project or use an existing one. Add the http package to your pubspec.yaml file:

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

Then, run flutter pub get to install the package.

Handling Different HTTP Request Methods

Here are examples of handling each HTTP request method in Flutter.

GET Request

Fetching data is typically done using a GET request:

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

Future<void> fetchData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  
  try {
    final response = await http.get(url);
    
    if (response.statusCode == 200) {
      final jsonData = jsonDecode(response.body);
      print('Data: $jsonData');
    } else {
      print('Failed to load data. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

POST Request

Submitting data to create a new resource:

Future<void> createPost() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  
  try {
    final response = await http.post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'title': 'Flutter POST Request',
        'body': 'This is the body of the POST request.',
        'userId': 1,
      }),
    );
    
    if (response.statusCode == 201) {
      final jsonData = jsonDecode(response.body);
      print('Post created: $jsonData');
    } else {
      print('Failed to create post. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

PUT Request

Updating an existing resource:

Future<void> updatePost() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  
  try {
    final response = await http.put(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'id': 1,
        'title': 'Flutter PUT Request',
        'body': 'This is the updated body of the PUT request.',
        'userId': 1,
      }),
    );
    
    if (response.statusCode == 200) {
      final jsonData = jsonDecode(response.body);
      print('Post updated: $jsonData');
    } else {
      print('Failed to update post. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

DELETE Request

Deleting a resource:

Future<void> deletePost() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  
  try {
    final response = await http.delete(url);
    
    if (response.statusCode == 200) {
      print('Post deleted successfully');
    } else {
      print('Failed to delete post. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

PATCH Request

Partially updating a resource:

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

  try {
    final response = await http.patch(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'title': 'Flutter PATCH Request',
      }),
    );

    if (response.statusCode == 200) {
      final jsonData = jsonDecode(response.body);
      print('Post patched: $jsonData');
    } else {
      print('Failed to patch post. Status code: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

Handling HTTP Status Codes

It is crucial to handle HTTP status codes correctly to provide meaningful feedback to the user and manage errors effectively. Here’s how to handle different status codes:

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

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

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

    switch (response.statusCode) {
      case 200:
        final jsonData = jsonDecode(response.body);
        print('Success: $jsonData');
        break;
      case 400:
        print('Bad Request: ${response.body}');
        break;
      case 401:
        print('Unauthorized: ${response.body}');
        break;
      case 404:
        print('Not Found');
        break;
      case 500:
        print('Internal Server Error');
        break;
      default:
        print('Unexpected status code: ${response.statusCode}');
        break;
    }
  } catch (e) {
    print('Error: $e');
  }
}

In this example, a switch statement is used to handle different status codes, providing specific feedback based on each code.

Best Practices

  • Use try-catch blocks: Handle potential exceptions, such as network errors, while making HTTP requests.
  • Check status codes: Always validate the HTTP status codes to ensure that the request was successful.
  • Handle errors gracefully: Provide user-friendly error messages.
  • Use appropriate HTTP methods: Employ the correct method (GET, POST, PUT, DELETE) based on the action you intend to perform.
  • Set correct headers: Ensure your requests include necessary headers, like Content-Type.

Complete Example

Here is a complete example demonstrating how to make a GET request and handle the response:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter HTTP Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String data = 'Loading...';

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

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

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

      if (response.statusCode == 200) {
        final jsonData = jsonDecode(response.body);
        setState(() {
          data = jsonData['title'];
        });
      } else {
        setState(() {
          data = 'Failed to load data. Status code: ${response.statusCode}';
        });
      }
    } catch (e) {
      setState(() {
        data = 'Error: $e';
      });
    }
  }

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

In this example, the app fetches data from an API and displays the title. It also handles errors and displays appropriate messages based on the HTTP status code.

Conclusion

Handling different HTTP request methods and status codes in Flutter is essential for building reliable and user-friendly applications. By following the examples and best practices outlined in this article, you can effectively interact with RESTful APIs and manage different scenarios that arise during API communication. Always remember to validate status codes, handle errors gracefully, and use the appropriate HTTP methods to ensure the robustness of your Flutter applications.