Exploring HTTP Requests in Flutter
Tags: api, flutter, programming, beginners
Published: 2023-11-08 02:30:00

I'm excited to share insights into the world of HTTP requests in Flutter and how they play a vital role in mobile app development. Before diving into practical examples, I'd like to direct your attention to a couple of resources that can complement and expand your understanding of this topic.

I've created a YouTube video that delves into the very topic we're about to explore. In the video, I demonstrate the execution of HTTP requests in a Flutter environment, providing a visual guide that might enhance your understanding. You can find the video here. https://youtu.be/ml5Bv1zf6fI (Highly Recommended)

Additionally, I've curated a repository on GitHub where you can find relevant code snippets, supplementary materials, and resources related to the examples we'll be discussing. The GitHub repository is accessible here. https://github.com/raman04-byte/api_tutorial

Let's embark on this journey together, discovering the power and versatility of HTTP requests within Flutter and how they drive dynamic data interactions in mobile applications.

Utilizing the HTTP Package

Flutter's http package simplifies the process of making HTTP requests and handling responses. Let's explore two sample codes showcasing GET and POST requests.

Sample Code 1: Performing a POST Request

The following code demonstrates how to send data to a server using a POST request:
```
import 'dart:async';
import 'dart:convert';

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

Future<Album> createAlbum(String title) async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/albums'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  if (response.statusCode == 201) {
    // If the server did return a 201 CREATED response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 201 CREATED response,
    // then throw an exception.
    throw Exception('Failed to create album.');
  }
}

class Album {
  final int id;
  final String title;

  const Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  final TextEditingController _controller = TextEditingController();
  Future<Album>? _futureAlbum;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Create Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Create Data Example'),
        ),
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8),
          child: (_futureAlbum == null) ? buildColumn() : buildFutureBuilder(),
        ),
      ),
    );
  }

  Column buildColumn() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        TextField(
          controller: _controller,
          decoration: const InputDecoration(hintText: 'Enter Title'),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _futureAlbum = createAlbum(_controller.text);
            });
          },
          child: const Text('Create Data'),
        ),
      ],
    );
  }

  FutureBuilder<Album> buildFutureBuilder() {
    return FutureBuilder<Album>(
      future: _futureAlbum,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.title);
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }

        return const CircularProgressIndicator();
      },
    );
  }
}
```

This code is a simple Flutter application that demonstrates creating an album by sending a POST request to a mock API endpoint using the http package in Dart. Here's a breakdown of the major components:

Import Statements

Importing necessary Dart and Flutter packages like async, http, material, and json.

Album Class

Album class represents an album with an id and title. It contains a constructor and a fromJson factory method to convert JSON data into an Album object.

createAlbum Function

createAlbum is an asynchronous function that uses the http.post method to send a POST request to the specified API endpoint ('https://jsonplaceholder.typicode.com/albums').

It sends a JSON payload containing the album title in the request body.

If the request is successful (returns a status code of 201 - Created), it parses the response JSON and creates an Album object using the fromJson factory method.

If the request fails or returns a different status code, it throws an exception indicating the failure to create an album.

Main Function

main function sets up the Flutter application by running the MyApp widget.

MyApp Class

MyApp is a stateful widget that defines the root of the application.

_MyAppState Class

_MyAppState is the state associated with MyApp and contains the text controller for the input field and a Future<Album> object to handle the asynchronous creation of an album.

build Method

The build method sets up the UI of the application.

It configures the app's theme and defines the Scaffold with an AppBar and a body.

The body contains a container with a column widget, which contains a text field for entering the album title and a button to trigger the creation of the album.

buildColumn Method

buildColumn returns a column containing a text field and a button.

The button triggers the creation of the album by calling the createAlbum function when pressed.

buildFutureBuilder Method

buildFutureBuilder returns a FutureBuilder widget. It displays different UI elements based on the state of the asynchronous operation (_futureAlbum).

If the operation is complete and successful, it displays the title of the created album.

If there's an error during the operation, it displays the error message.

While the operation is in progress, it displays a circular progress indicator.

The application allows users to enter a title for an album, create it via a POST request, and displays the result or error message accordingly using Flutter's FutureBuilder.

This code showcases a function createAlbum that sends a POST request to a server. It includes an Album class representing the structure of an album and a UI setup in the MyApp class allowing users to input an album title and create an album.

Sample Code 2: Performing a GET Request

Let's take a look at the code that fetches data from a server using a GET request:

```
import 'package:apitutorial/model/response/list_of_response.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  List<ListOfData> _list = [];

  Future<List<ListOfData>> getAllData() async {
    try {
      final response =
          await http.get(Uri.parse('https://fakestoreapi.com/products'));
      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        _list = data.map<ListOfData>((e) => ListOfData.fromJson(e)).toList();
        debugPrint('${_list.length}');
        return _list;
      } else {
        debugPrint(
            'Error in API call Please check your backend and URL carefully');
      }
      return _list;
    } catch (e) {
      debugPrint('$e');
    }
    return _list;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
          future: getAllData(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                  itemCount: _list.length,
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text('${_list[index].title}'),
                      subtitle: Text('${_list[index].description}'),
                      leading: SizedBox(
                        height: 50,
                        width: 50,
                        child: CachedNetworkImage(
                          imageUrl: '${_list[index].image}',
                          progressIndicatorBuilder: (context, url, progress) =>
                              CircularProgressIndicator(
                                  value: progress.progress),
                        ),
                      ),
                      trailing: Text('${_list[index].price}'),
                    );
                  });
            } else if (snapshot.hasError) {
              return const Text("Error");
            }
            return const Text("No Data");
          }),
    );
  }
}
```

This code represents a Flutter StatefulWidget named HomeScreen that fetches data from a specified API endpoint and displays the information in a ListView.

Here's a breakdown of the code:

Import Statements

Various package imports are included such as material from Flutter, http for making HTTP requests, cached_network_image for efficiently loading and caching network images, and json for encoding and decoding JSON data.

HomeScreen Class

HomeScreen is a StatefulWidget representing the main screen of the application.

_HomeScreenState Class

_HomeScreenState manages the state for the HomeScreen.

State Variables

_list is a list of ListOfData objects.

getAllData Method

getAllData is an asynchronous function that makes an HTTP GET request to 'https://fakestoreapi.com/products' to fetch data.

If the response status is 200 (OK), the JSON response is decoded and used to populate the _list by mapping the JSON data to ListOfData objects (presumably defined in 'list_of_response.dart').

Debug print statements are used to display the count of items fetched or any error encountered during the API call.

build Method

The build method configures the UI for the HomeScreen.

It displays a Scaffold containing a FutureBuilder that waits for the result of getAllData.

When data is received, it displays a ListView.builder widget, populating the list with data fetched from the API.

Each list item is represented by a ListTile containing text fields for the title, description, and price. It also includes a CachedNetworkImage widget for loading and displaying the product image.

The FutureBuilder is responsible for showing different UI components based on the current state of the asynchronous operation:

If data is available, it displays the list of items fetched from the API.

If there is an error during the API call, it shows an error message.

If no data is available, it displays a message indicating the absence of data.

This code provides a basic structure to fetch data from an API and display it in a list format, including images loaded from network URLs using the cached_network_image package to ensure efficient caching and loading of images in the Flutter app.

Both sample codes illustrate practical implementations of making HTTP requests in Flutter using the http package. The first code focuses on creating data through a POST request, while the second code emphasizes retrieving and displaying data via a GET request.

Continuing the article with a discussion on best practices, error handling, and the nuances of using Flutter's HTTP library would complement the multimedia content and provide a comprehensive guide for readers interested in networking with Flutter applications.