Flutter Course: Using the HTTP Package

Author: Your Name | Date: October 2023

Introduction

Flutter is an open-source UI software development kit (SDK) created by Google,
which allows rapid development of applications that run on multiple platforms using a single codebase.
In this tutorial, we will explore the http package, commonly used to communicate with external APIs in Flutter.
This package is a tool that simplifies communication with RESTful APIs.

Installing the http Package

To use the http package, you first need to add it to your pubspec.yaml file.
Add the following code to the dependencies: section:

dependencies:
  http: ^0.13.4

After adding the package, use the following command to install it:

flutter pub get

Basic Usage

To use the http package, you first need to import the relevant classes.
Write your code like this to use the http package:

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

Now, let’s look at how to send a GET request to an external API.
For example, checking user information from the JSONPlaceholder API:

Future fetchData() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
  
  if (response.statusCode == 200) {
    // When the request was successful
    print('Data: ${response.body}');
  } else {
    // When the request failed
    throw Exception('Failed to fetch data.');
  }
}

The function above operates asynchronously,
it requests user information from the API, printing the data if the response is successful.

Sending a POST Request

Now, let’s look at how to send a POST request.
For example, when sending data to an API that creates a new user:

Future createUser() async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/users'),
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode({
      'name': 'John Doe',
      'username': 'johndoe',
      'email': 'johndoe@example.com',
    }),
  );

  if (response.statusCode == 201) {
    // User was successfully created.
    print('User created: ${response.body}');
  } else {
    // When the request failed
    throw Exception('Failed to create user');
  }
}

In the above code, the `jsonEncode` function is a built-in JSON encoding function in Dart,
used to convert Dart objects into JSON-formatted strings.

Query Parameters and Headers

Let’s learn how to add query parameters in HTTP GET requests
and how to set request headers.
For example, you can filter data based on certain conditions:

Future fetchFilteredData() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users?filter=active'), 
    headers: {
      'Authorization': 'Bearer some_api_key',
    }
  );

  if (response.statusCode == 200) {
    print('Filtered data: ${response.body}');
  } else {
    throw Exception('Failed to fetch filtered data.');
  }
}

Here, `filter=active` is a query parameter provided by the API,
and the `Authorization` header is a way to provide authentication information to the server by including the API key.

Error Handling

When making API requests, you should always handle errors.
By checking the HTTP request’s status code and handling exceptions,
you can provide a better experience for users:

Future fetchDataWithErrorHandling() async {
  try {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));

    if (response.statusCode == 200) {
      print('Data: ${response.body}');
    } else {
      throw Exception('Server Error: ${response.statusCode}');
    }
  } catch (e) {
    print('Error occurred during request: $e');
  }
}

The above code uses a try-catch statement to
handle potential exceptions that may occur during asynchronous requests.

Reusing the HTTP Client

Reusing the HTTP client can optimize performance and
allow for common use across multiple requests.
You can create an HTTP client like this:

class ApiService {
  final http.Client client;

  ApiService(this.client);
  
  Future fetchData() async {
    final response = await client.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
    // Same data processing logic as above...
  }
}

// Example usage:
final apiService = ApiService(http.Client());
await apiService.fetchData();

By injecting the client into a class like this,
you can improve reusability and ease of testing.

Parsing JSON Data

You can parse JSON data received from an API for use.
It’s common practice to create model classes to consume the data internally:

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

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

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

The above model class shows how
to convert JSON data into objects.

Handling List Data

Let’s learn how to handle multiple JSON objects as a list.
To do this, you’ll need to properly transform the data received from the API:

Future> fetchUsers() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
  
  if (response.statusCode == 200) {
    List jsonData = jsonDecode(response.body);
    return jsonData.map((data) => User.fromJson(data)).toList();
  } else {
    throw Exception('Failed to fetch user list.');
  }
}

This code parses the JSON data received from the server and
returns a list of user information.

Handling HTTP Redirection

Certain API requests may require handling redirection.
In this case, it is automatically handled when using http.Client,
but let’s also look at how to manually handle redirection:

Future handleRedirect() async {
  final response = await http.get(Uri.parse('https://httpbin.org/redirect/1'));

  if (response.statusCode == 200) {
    print('Final URL: ${response.request!.url}');
  } else {
    print('Request failed: ${response.statusCode}');
  }
}

In the example above, it will automatically follow redirection for the HTTP request and output the final URL.

Comprehensive Example: Creating a CRUD Application

Based on what we have learned so far, let’s discuss how to implement a simple CRUD (Create, Read, Update, Delete) application.
For example, you can implement functionalities to add, retrieve, update, and delete users using the JSONPlaceholder API.

class UserApiService {
  final http.Client client;
  
  UserApiService(this.client);
  
  Future> fetchUsers() async {
    // Code to fetch users...
  }

  Future createUser(User user) async {
    // Code to create a user...
  }

  Future updateUser(User user) async {
    // Code to update a user...
  }

  Future deleteUser(int id) async {
    final response = await client.delete(Uri.parse('https://jsonplaceholder.typicode.com/users/$id'));
    // Logic for deletion...
  }
}

The above example defines a `UserApiService` class that includes methods for CRUD operations.
You can add functionalities by implementing actual HTTP requests.

Through this tutorial, you have gained an understanding of how to use the http package in Flutter and learned how to expand the functionality of Flutter applications through communication with RESTful APIs.
Expect more examples and advanced content in the next tutorial!