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
- Server-side REST API tutorial from our previous article.
- C# REST API Client , Python REST API Client and Java REST API Client were built previously, showing another approach for interacting with the API with different language.
- A downloadable Postman collection for testing the API directly.
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