Flutter REST API Series

Accessing REST API

Accessing REST API

Accessing a REST API means making a request to a web service that follows the principles of Representational State Transfer (REST) to send or receive data. REST APIs allow applications to interact with a server over HTTP methods like GET, POST, PUT, and DELETE to perform operations such as fetching, updating, or deleting data.

  • GET: Retrieve data from the server.
  • POST: Send data to the server.
  • PUT: Update existing data on the server.
  • DELETE: Remove data from the server.

What Are REST APIs?

REST (Representational State Transfer) is a way for applications to communicate over the internet using HTTP methods like GET, POST, PUT, and DELETE. APIs (Application Programming Interfaces) expose endpoints that allow us to access or manipulate data.

Key Characteristics of Accessing REST APIs:

  1. Stateless Communication: Each API request is independent and does not rely on previous requests.
  2. JSON Format: Data is typically exchanged in lightweight and human-readable JSON format.
  3. Endpoint-Based: Each functionality (e.g., users, posts, products) is accessed through unique URLs called endpoints.
  4. Platform-Agnostic: REST APIs can be accessed from any platform (mobile, web, desktop) that supports HTTP requests.

Add http Package to Your Project

Include the http package in your pubspec.yaml file:


dependencies:
http: ^1.0.0

Import the http Package

At the top of your Dart file, import the http package:


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

Make a GET Request

Here’s an example of making a GET request to fetch data from a REST API:


Future<void> fetchData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  
  try {
    final response = await http.get(url);
    if (response.statusCode == 200) {
      // Decode the JSON response
      final data = json.decode(response.body);
      print('Data fetched successfully: ' + data);
    } else {
      print('Failed to fetch data. Status code: ' + response.statusCode);
    }
  } catch (e) {
    print('Error: ' + e);
  }
}
  

Make a GET Request

Here’s an example of making a GET request to fetch data from a REST API:


Future<void> fetchData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  
  try {
    final response = await http.get(url);
    if (response.statusCode == 200) {
      // Decode the JSON response
      final data = json.decode(response.body);
      print('Data fetched successfully: ' + data);
    } else {
      print('Failed to fetch data. Status code: ' + response.statusCode);
    }
  } catch (e) {
    print('Error: ' + e);
  }
}
  

Make a POST Request

If you need to send data to the server, use a POST request:


Future<void> sendData(Map<String, dynamic> data) async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  
  try {
    final response = await http.post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: json.encode(data),
    );
    if (response.statusCode == 201) {
      print('Data sent successfully: ' + response.body);
    } else {
      print('Failed to send data. Status code: ' + response.statusCode);
    }
  } catch (e) {
    print('Error: ' + e);
  }
}
  

Calling the Functions

You can call these functions in your app’s lifecycle (e.g., initState) or based on user interaction:


  @override
void initState() {
  super.initState();
  fetchData();
  
  sendData({
    'title': 'Flutter REST API',
    'body': 'This is a post request example.',
    'userId': 1,
  });
}
  

Using the Data

Once you fetch the data, you can use it in your UI. For example:


List<dynamic> posts = [];

Future<void> fetchPosts() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  try {
    final response = await http.get(url);
    if (response.statusCode == 200) {
      setState(() {
        posts = json.decode(response.body);
      });
    }
  } catch (e) {
    print('Error: ' + e);
  }
}
  

Display the fetched data in a ListView:


@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Flutter REST API')),
    body: posts.isEmpty
        ? Center(child: CircularProgressIndicator())
        : ListView.builder(
            itemCount: posts.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(posts[index]['title']),
                subtitle: Text(posts[index]['body']),
              );
            },
          ),
  );
}
  

Adding a New Product (POST Request)

Write the Code for POST Request:


Future<void> addProduct(String name, double price) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/products'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY',
    },
    body: jsonEncode({
      'name': name,
      'price': price,
    }),
  );

  if (response.statusCode == 201) {
    print('Product added successfully');
  } else {
    throw Exception('Failed to add product');
  }
}
  

Call the Function


ElevatedButton(
  onPressed: () {
    addProduct('New Product', 19.99);
  },
  child: Text('Add Product'),
);
  

Deleting a Product (DELETE Request)

Write the Code for DELETE Request


Future<void> deleteProduct(int id) async {
  final response = await http.delete(
    Uri.parse('https://api.example.com/products/' + id.toString()),
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
    },
  );

  if (response.statusCode == 200) {
    print('Product deleted successfully');
  } else {
    throw Exception('Failed to delete product');
  }
}
  

Call the Function


ElevatedButton(
  onPressed: () {
    deleteProduct(1);
  },
  child: Text('Delete Product'),
);
  

Testing the API

Before integrating with your app, test the API endpoints using tools like:

  1. Postman: A GUI-based tool for making API requests.
  2. cURL Command-line-based tool for testing APIs

Error Handling and Debugging

  • Always handle errors gracefully and display meaningful messages to users.
  • Use try-catch blocks to handle exceptions in your Flutter app.
  • 
    try {
      final products = await fetchProducts();
      print(products);
    } catch (e) {
      print('Error: ' + e);
    }
      

  • Create a products.json file with product information.

  • 
    [
      {
        "id": 1,
        "name": "Wireless Headphones",
        "price": 59.99,
        "category": "Electronics",
        "description": "High-quality wireless headphones with noise cancellation.",
        "stock": 120
      },
      {
        "id": 2,
        "name": "Gaming Mouse",
        "price": 29.99,
        "category": "Accessories",
        "description": "Ergonomic gaming mouse with customizable buttons.",
        "stock": 85
      },
      {
        "id": 3,
        "name": "Smartwatch",
        "price": 149.99,
        "category": "Wearable",
        "description": "Smartwatch with heart rate monitoring and GPS.",
        "stock": 60
      },
      {
        "id": 4,
        "name": "Laptop",
        "price": 999.99,
        "category": "Computers",
        "description": "Powerful laptop with 16GB RAM and 512GB SSD.",
        "stock": 25
      },
      {
        "id": 5,
        "name": "Electric Kettle",
        "price": 24.99,
        "category": "Home Appliances",
        "description": "Fast-boiling electric kettle with auto shut-off feature.",
        "stock": 150
      }
    ]
      

    Here you can create a new Product.dart file and define the Product class with a factory constructor for converting mapped data into a Product object.

    Step 1: Create the File

    1. In your Flutter project, navigate to the lib folder.
    2. Create a new file named Product.dart.

    Step 2: Add the Product Class

    Copy and paste the following code into the Product.dart file:

    
    class Product {
      final int id;
      final String name;
      final double price;
      final String category;
      final String description;
      final int stock;
    
      // Constructor
      Product({
        required this.id,
        required this.name,
        required this.price,
        required this.category,
        required this.description,
        required this.stock,
      });
    
      // Factory constructor to create a Product object from a Map
      factory Product.fromMap(Map<String, dynamic> map) {
        return Product(
          id: map['id'] as int,
          name: map['name'] as String,
          price: (map['price'] as num).toDouble(),
          category: map['category'] as String,
          description: map['description'] as String,
          stock: map['stock'] as int,
        );
      }
    
      // Method to convert Product object back to a Map (optional)
      Map<String, dynamic> toMap() {
        return {
          'id': id,
          'name': name,
          'price': price,
          'category': category,
          'description': description,
          'stock': stock,
        };
      }
    }
      

    Step 3: Usage Example

    Here’s how you can use the Product class to parse the data from your products.json file:


    1. Import the File

    In the file where you’re handling the JSON data (e.g., main.dart), import the Product.dart file:

    import 'Product.dart';


    2. Parse the JSON Data

    Suppose you’ve loaded the JSON data into a Dart List or Map. Use the Product.fromMap factory constructor to convert the data into a list of Product objects.

    
    import 'dart:convert';
    import 'Product.dart';
    
    void main() {
      // Example JSON data as a string
      String jsonData = '''
      [
        {
          "id": 1,
          "name": "Wireless Headphones",
          "price": 59.99,
          "category": "Electronics",
          "description": "High-quality wireless headphones with noise cancellation.",
          "stock": 120
        },
        {
          "id": 2,
          "name": "Gaming Mouse",
          "price": 29.99,
          "category": "Accessories",
          "description": "Ergonomic gaming mouse with customizable buttons.",
          "stock": 85
        }
      ]
      ''';
    
      // Decode JSON string into a List of Maps
      List<dynamic> productList = json.decode(jsonData);
    
      // Convert List of Maps into List of Product objects
      List<Product> products = productList.map((map) => Product.fromMap(map)).toList();
    
      // Print Product objects
      for (var product in products) {
        print('Product: \${product.name}, Price: \$\${product.price}');
      }
    }
      

    Output

    Product: Wireless Headphones, Price: $59.99

    Product: Gaming Mouse, Price: $29.99


    Advantages
    1. The factory constructor simplifies converting JSON data into Dart objects.
    2. The toMap method can help if you need to send Product objects back to a server.

    Below is how you can write the methods parseProducts and fetchProducts in the main class to load product information from a web server into a List Product>< object.

    
    // Import statements
    import 'dart:convert'; // For JSON encoding/decoding
    import 'package:http/http.dart' as http; // For making HTTP requests
    import 'Product.dart'; // Import the Product class
    
    class ProductService {
      // Method to parse the JSON response and return a List Product ⁢
      List<Product> parseProducts(String responseBody) {
        final List<dynamic> parsed = json.decode(responseBody); // Decode JSON string into a List
        return parsed.map<Product>((map) => Product.fromMap(map)).toList(); // Map each item to a Product
      }
    
      // Method to fetch products from the web server
      Future<List<Product>> fetchProducts(String url) async {
        try {
          final response = await http.get(Uri.parse(url)); // Make GET request to the given URL
    
          if (response.statusCode == 200) {
            // If the server returns a successful response
            return parseProducts(response.body); // Parse the response body into List Product ⁢
          } else {
            // Handle non-200 responses
            throw Exception('Failed to load products. Status Code: \${response.statusCode}');
          }
        } catch (e) {
          // Handle any exceptions
          throw Exception('Failed to load products: \$e');
        }
      }
    }
      

    Here’s how you can use the FutureBuilder widget in main.dart to fetch and display product data. If the future property of FutureBuilder returns data successfully, it will display the ProductBoxList widget. If an error occurs, it will display the error message.

    Complete Code of main.dart

    
    // Import statements
    import 'package:flutter/material.dart';
    import 'ProductService.dart'; // Import the ProductService
    import 'Product.dart'; // Import the Product class
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Product List',
          home: ProductPage(),
        );
      }
    }
    
    class ProductPage extends StatelessWidget {
      final ProductService productService = ProductService(); // Create an instance of ProductService
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Product List'),
          ),
          body: FutureBuilder<List<Product>>(
            future: productService.fetchProducts('https://example.com/api/products'), // Replace with your API endpoint
            builder: (context, snapshot) {
              // Check connection state
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Center(child: CircularProgressIndicator()); // Show loading spinner
              } else if (snapshot.hasError) {
                return Center(child: Text('Error: \${snapshot.error}')); // Display error
              } else if (snapshot.hasData) {
                return ProductBoxList(products: snapshot.data!); // Render ProductBoxList
              } else {
                return Center(child: Text('No data available')); // Handle empty data
              }
            },
          ),
        );
      }
    }
    
    // Widget to display the list of products
    class ProductBoxList extends StatelessWidget {
      final List<Product> products;
    
      ProductBoxList({required this.products});
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            final product = products[index];
            return Card(
              margin: EdgeInsets.all(8.0),
              child: ListTile(
                leading: Icon(Icons.shopping_bag, size: 40.0, color: Colors.blue),
                title: Text(product.name, style: TextStyle(fontWeight: FontWeight.bold)),
                subtitle: Text('Price: \$\${product.price}'),
                trailing: Text('Stock: \${product.stock}'),
              ),
            );
          },
        );
      }
    }
    
    أحدث أقدم