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:
- Stateless Communication: Each API request is independent and does not rely on previous requests.
- JSON Format: Data is typically exchanged in lightweight and human-readable JSON format.
- Endpoint-Based: Each functionality (e.g., users, posts, products) is accessed through unique URLs called endpoints.
- 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:
- Postman: A GUI-based tool for making API requests.
- cURL Command-line-based tool for testing APIs
Error Handling and Debugging
try {
final products = await fetchProducts();
print(products);
} catch (e) {
print('Error: ' + e);
}
[
{
"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
- In your Flutter project, navigate to the lib folder.
- 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
- The factory constructor simplifies converting JSON data into Dart objects.
- 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}'),
),
);
},
);
}
}