Working with REST APIs in Flutter

Flutter has become a popular choice for cross-platform mobile app development, thanks to its ease of use, rich set of widgets, and excellent performance. One common requirement in mobile apps is fetching data from REST APIs. This blog post will guide you through the process of working with REST APIs in Flutter, including how to make HTTP requests, handle responses, parse JSON data, and display it in your app.

What are REST APIs?

REST (Representational State Transfer) APIs are a widely used architectural style for designing networked applications. They allow different software systems to communicate over the internet. A REST API typically uses HTTP methods (GET, POST, PUT, DELETE) to perform operations on resources identified by URLs.

Why Work with REST APIs in Flutter?

  • Dynamic Data: Fetch real-time data from servers to keep your app updated.
  • Cross-Platform Compatibility: Flutter apps can connect to any REST API, regardless of the backend technology.
  • Feature Integration: Access external services, such as authentication, payment gateways, and social media platforms.

Setting Up Your Flutter Project

Before diving into the code, make sure you have a Flutter project set up. If not, create a new Flutter project using the following command:

flutter create flutter_rest_api_example

Step 1: Add the http Package

To make HTTP requests in Flutter, you’ll need the http package. Add it to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.5

Run flutter pub get to install the package.

Step 2: Making a GET Request

Let’s start by making a simple GET request to fetch data from a REST API. We’ll use the JSONPlaceholder API, which provides fake online REST APIs for testing and prototyping.

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 REST API Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

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

class _MyHomePageState extends State {
  final String apiUrl = 'https://jsonplaceholder.typicode.com/todos/1';
  
  Future> fetchData() async {
    final response = await http.get(Uri.parse(apiUrl));
    
    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.
      return jsonDecode(response.body);
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to load data');
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('REST API Example'),
      ),
      body: Center(
        child: FutureBuilder>(
          future: fetchData(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('User ID: ${snapshot.data!['userId']}'),
                  Text('ID: ${snapshot.data!['id']}'),
                  Text('Title: ${snapshot.data!['title']}'),
                  Text('Completed: ${snapshot.data!['completed']}'),
                ],
              );
            } else if (snapshot.hasError) {
              return Text('${snapshot.error}');
            }
            
            // By default, show a loading spinner.
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

In this code:

  • We import the necessary packages: flutter/material.dart, http/http.dart, and dart:convert.
  • The fetchData function sends a GET request to the API endpoint and parses the JSON response.
  • We use a FutureBuilder to handle the asynchronous nature of the HTTP request and display the data when it’s available.

Step 3: Making a POST Request

To send data to a REST API, you can use a POST request. Here’s how to make a POST request in Flutter:

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

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

class _MyHomePageState extends State {
  final String apiUrl = 'https://jsonplaceholder.typicode.com/posts';
  
  Future createPost(String title, String body, int userId) async {
    final response = await http.post(
      Uri.parse(apiUrl),
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode({
        'title': title,
        'body': body,
        'userId': userId,
      }),
    );
    return response;
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('REST API Example - POST'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Create Post'),
          onPressed: () async {
            final response = await createPost('My Title', 'My Body', 1);
            if (response.statusCode == 201) {
              // If the server did return a 201 CREATED response,
              // then show a success message.
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Post created successfully!')),
              );
            } else {
              // If the server did not return a 201 CREATED response,
              // then show an error message.
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Failed to create post.')),
              );
            }
          },
        ),
      ),
    );
  }
}

In this example:

  • The createPost function sends a POST request to the API endpoint with the provided data in JSON format.
  • We set the Content-Type header to application/json to indicate that we’re sending JSON data.
  • The response status code is checked to determine if the POST request was successful.

Step 4: Handling Errors

When working with REST APIs, it’s essential to handle potential errors gracefully. Here’s how you can handle errors in your Flutter app:

Future> fetchData() async {
  try {
    final response = await http.get(Uri.parse(apiUrl));
    
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    } else {
      throw Exception('Failed to load data: Status code ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
    throw Exception('Failed to load data');
  }
}

Here, we use a try-catch block to catch any exceptions that may occur during the HTTP request. We also check the status code to ensure the request was successful. If an error occurs, we print the error message to the console and throw an exception, which can be handled by the FutureBuilder.

Step 5: Parsing Complex JSON

Sometimes, the JSON response from an API can be complex, with nested objects and arrays. Here’s how you can parse complex JSON in Flutter:

import 'dart:convert';

class User {
  final int userId;
  final int id;
  final String title;
  final bool completed;
  
  User({
    required this.userId,
    required this.id,
    required this.title,
    required this.completed,
  });
  
  factory User.fromJson(Map json) {
    return User(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      completed: json['completed'],
    );
  }
}

Future fetchUser() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
  
  if (response.statusCode == 200) {
    return User.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load user');
  }
}

In this example:

  • We create a User class to represent the JSON data.
  • The User.fromJson factory method is used to parse the JSON and create a User object.

Conclusion

Working with REST APIs in Flutter involves making HTTP requests, handling responses, parsing JSON data, and displaying it in your app. By using the http package and following the steps outlined in this blog post, you can easily integrate REST APIs into your Flutter projects. Remember to handle errors gracefully and parse JSON data correctly to ensure your app works smoothly and efficiently. This approach allows you to build dynamic and data-driven applications that provide a rich user experience.