Building a REST API Client in Flutter and Dart

In this tutorial, we’ll create a Flutter application that performs CRUD (Create, Read, Update, Delete) operations on a REST API. This app will interact with a backend API to manage product data, demonstrating the basics of HTTP requests, handling responses, and updating UI components.

Prerequisites

    1. Flutter Setup: Ensure Flutter is installed and set up.
    2. API Ready: You should have a REST API server set up. (If not, you can check out our previous tutorial on setting up a server-side API with PHP as a prerequisite.)
    3. Dependencies: Install the http package in pubspec.yaml to make HTTP requests.

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.0

Step 1: Setting Up the Model

To manage products in our Flutter app, let’s create a model class that will represent a product entity.
// models/product.dart
class Product {
  final int id;
  final String name;
  final String description;
  final double price;

  Product({required this.id, required this.name, required this.description, required this.price});

  // Factory method to create a Product from JSON
  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] ?? 0,
      name: json['name'],
      description: json['description'],
      price: double.tryParse(json['price']) ?? 0.0,
); } // Method to convert a Product instance to JSON Map<String, dynamic> toJson() { return { 'name': name, 'description': description, 'price': price, }; } }

Step 2: Implementing the CRUD Operations

We’ll create a ProductService class to handle the HTTP requests. Each function will correspond to a CRUD operation.

// services/product_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/product.dart';

class ProductService {
  //static const String apiUrl = 'http://localhost:8000/api/products';
  static const String apiUrl = 'https://bcssti.com/api-sample/products/';

  // GET: Fetch all products
  Future<List<Product>> getAllProducts() async {
    final response = await http.get(Uri.parse(apiUrl), headers: {
      'Cache-Control': 'no-cache',
    });

    if (response.statusCode == 200) {
      List jsonResponse = json.decode(response.body);
      return jsonResponse.map((data) => Product.fromJson(data)).toList();
    } else {
      throw Exception('Failed to load products');
    }
  }

  // POST: Add a new product
  Future<Product> createProduct(String name, String description, double price) async {
    final response = await http.post(
      Uri.parse(apiUrl),
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'no-cache',
      },
      body: json.encode({'name': name, 'description': description, 'price': price}),
    );

    if (response.statusCode == 200) {
      return Product.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to create product');
    }
  }

  // PUT: Update a product by ID
  Future<Product> updateProduct(int id, String name, String description, double price) async {
    final response = await http.put(
      Uri.parse('$apiUrl?id=$id'),
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'no-cache',
      },
      body: json.encode({'name': name, 'description': description, 'price': price}),
    );

    if (response.statusCode == 200) {
      return Product.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to update product');
    }
  }

  // DELETE: Remove a product by ID
  Future<void> deleteProduct(int id) async {
    final response = await http.delete(
      Uri.parse('$apiUrl?id=$id'),
      headers: {
        'Cache-Control': 'no-cache',
      },
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to delete product');
    }
  }
}

Step 3: Setting Up the UI

In the UI, we’ll add buttons to perform each operation (Fetch, Create, Update, and Delete) and display the products in a list.

// main.dart
import 'package:flutter/material.dart';
import 'models/product.dart';
import 'services/product_service.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Product API Client',
      home: ProductListScreen(),
    );
  }
}

class ProductListScreen extends StatefulWidget {
  @override
  _ProductListScreenState createState() => _ProductListScreenState();
}

class _ProductListScreenState extends State<ProductListScreen> {
  final ProductService productService = ProductService();
  List<Product> products = [];

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

  void _fetchProducts() async {
    final fetchedProducts = await productService.getAllProducts();
    setState(() {
      products = fetchedProducts;
    });
  }

  void _createProduct(String name, String description, double price) async {
    await productService.createProduct(name, description, price);
    _fetchProducts();
  }

  void _updateProduct(int id, String name, String description, double price) async {
    await productService.updateProduct(id, name, description, price);
    _fetchProducts();
  }

  void _deleteProduct(int id) async {
    await productService.deleteProduct(id);
    _fetchProducts();
  }

  void _showDialog(Product product, bool add) {

    final nameController = TextEditingController(text: product.name);
    final descriptionController = TextEditingController(text: product.description);
    final priceController = TextEditingController(text: product.price.toString());

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text('Update Product'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: nameController,
                decoration: InputDecoration(labelText: 'Name'),
              ),
              TextField(
                controller: descriptionController,
                decoration: InputDecoration(labelText: 'Description'),
              ),
              TextField(
                controller: priceController,
                decoration: InputDecoration(labelText: 'Price'),
                keyboardType: TextInputType.number,
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                final name = nameController.text;
                final description = descriptionController.text;
                final price = double.tryParse(priceController.text) ?? product.price;
                if (add) {
                  _createProduct(name, description, price);
                } else {
                  _updateProduct(product.id, name, description, price);
                }
                Navigator.of(context).pop();
              },
              child: Text(add ? 'Add' : 'Update'),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Products API Client')),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text(product.price.toStringAsFixed(2)),
            trailing: IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () => _deleteProduct(product.id),
            ),
            onTap: () => _showDialog(product, false),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () => _showDialog(Product(id: 0, name: '', description: '', price: 0), true),
      ),
    );
  }
}

Explanation of CRUD Functions in Flutter

    1. Fetch Products (getAllProducts): The getAllProducts method retrieves all products from the API using a GET request. We parsed the JSON response and updated the products list in the UI.
    2. Create Product (createProduct): The createProduct method sends a POST request with a JSON body containing product data. Once created, the product list is refreshed.
    3. Update Product (updateProduct): This method sends a PUT request with updated product details. After successfully updating, the UI is refreshed to show the latest data.
    4. Delete Product (deleteProduct): The deleteProduct method uses a DELETE request to remove a product by ID. The UI is refreshed after deletion.

Additional Resources


With this setup, you’ve built a simple and complete CRUD-enabled Flutter app that interacts with a REST API. This tutorial provides a solid foundation for handling data in Flutter with an HTTP-based backend
Previous Post Next Post

نموذج الاتصال