Flutter - Accessing REST API
Flutter - Accessing REST APIs
Section titled “Flutter - Accessing REST APIs”Modern mobile applications frequently interact with backend services through REST APIs to fetch or send data. Flutter provides robust tools for making HTTP requests.
The http Package
Section titled “The http Package”The core package for basic HTTP requests is package:http. It’s a Future-based library leveraging Dart’s async and await for handling asynchronous network operations.
Key features and methods:
- Top-level Functions: Provides simple functions for common requests:
-
http.get(url, headers: ...): Sends a GET request.
-
http.post(url, headers: ..., body: ..., encoding: ...): Sends a POST request, often with a request body (e.g., JSON).
-
http.put(url, headers: ..., body: ...): Sends a PUT request.
-
http.patch(url, headers: ..., body: ...): Sends a PATCH request.
-
http.delete(url, headers: ...): Sends a DELETE request.
-
http.read(url, headers: ...): Simple GET request that returns the response body as aString.
ResponseObject: Most methods return aFuture<Response>. TheResponseobject contains:-
statusCode: The HTTP status code (e.g., 200 for OK, 404 for Not Found).
-
body: The response body as aString.
-
headers: A map of response headers.
-
contentLength: The length of the response body.
- JSON Handling: You typically need
dart:convertto encode Dart objects (like Maps) into JSON strings for request bodies and decode JSON strings from response bodies into Dart objects. - Error Handling: Network errors or non-2xx status codes should be handled using
try-catchblocks and checking theresponse.statusCode.
import 'dart:convert'; // For jsonDecode, jsonEncodeimport 'package:http/http.dart' as http;
Future<void> fetchData() async { final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); // Use Uri.parse try { final response = await http.get(url);
if (response.statusCode == 200) { // Success! final data = jsonDecode(response.body) as Map<String, dynamic>; print('Title: ${data['title']}'); } else { // Server returned an error status code print('Request failed with status: ${response.statusCode}.'); } } catch (e) { // Handle network errors or other exceptions print('An error occurred: $e'); }}Alternative: dio Package
Section titled “Alternative: dio Package”While http is great for basic needs, the dio package is a popular alternative offering more advanced features like interceptors (for logging, authentication), global configuration, FormData support, request cancellation, and more streamlined error handling. Consider dio for more complex API interactions.
Example: Fetching Products from a REST API
Section titled “Example: Fetching Products from a REST API”Let’s adapt the product app to fetch data from a simple JSON REST endpoint.
- Create Project:
flutter create product_rest_app. - Add
httpDependency: Addhttp: ^1.1.0(or latest) topubspec.yamland runflutter pub get. - Add Assets: (Optional, if using local images) Create
assets/images/, add images, declare inpubspec.yaml. - Prepare JSON Data: Create a
products.jsonfile with your product data:
[ { "id": 1, "name": "iPhone", "description": "The stylish phone ever", "price": 1000, "image": "iphone.png" }, { "id": 2, "name": "Pixel", "description": "The most feature phone ever", "price": 800, "image": "pixel.png" }, { "id": 3, "name": "Laptop", "description": "Most productive development tool", "price": 2000, "image": "laptop.png" } // ... more products]- Set up a Simple Web Server: You need a way to serve this
products.jsonfile over HTTP. -
- Node.js
http-server:
- Node.js
-
* Install Node.js if you haven't.
-
* Install the server: `npm install -g http-server`
-
* Navigate to the directory containing `products.json` in your terminal.
-
* Run: `http-server . -p 8000 --cors` (The `--cors` flag is important for web testing). Note the IP address provided (e.g., `http://192.168.1.10:8000`).
-
- Python:
-
* Navigate to the directory containing `products.json`.
-
* Run: `python -m http.server 8000` (Python 3). Use `127.0.0.1:8000` or your machine's local network IP.
-
- Other: Use any simple web server (Apache, Nginx, VS Code Live Server extension). Make sure it’s accessible from your emulator/device.
- Define
ProductModel (lib/product.dart): Include a factory constructor to parse JSON.
class Product { final int id; final String name; final String description; final int price; final String image;
const Product({ required this.id, required this.name, required this.description, required this.price, required this.image, });
// Factory constructor to create a Product from a JSON map factory Product.fromJson(Map<String, dynamic> json) { return Product( id: json['id'] as int, name: json['name'] as String, description: json['description'] as String, price: json['price'] as int, image: json['image'] as String, ); }}- Create API Service (
lib/api_service.dart): Encapsulate the fetching logic.
import 'dart:convert';import 'package:http/http.dart' as http;import 'product.dart'; // Assuming product.dart is in lib/
class ApiService { // Replace with your actual server URL // For Android emulator accessing localhost: use 10.0.2.2 // For physical device/iOS simulator: use your machine's local network IP static const String _baseUrl = 'http://10.0.2.2:8000'; // Example for Android emulator // static const String _baseUrl = 'http://192.168.1.10:8000'; // Example for local network IP
Future<List<Product>> fetchProducts() async { final url = Uri.parse('$_baseUrl/products.json'); try { final response = await http.get(url);
if (response.statusCode == 200) { // If the server returns an OK response, parse the JSON. List<dynamic> body = jsonDecode(response.body); List<Product> products = body .map((dynamic item) => Product.fromJson(item as Map<String, dynamic>)) .toList(); return products; } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load products (Status code: ${response.statusCode})'); } } catch (e) { // Handle network errors or parsing errors print('Error fetching products: $e'); throw Exception('Failed to load products: $e'); } }
// Add methods for POST, PUT, DELETE etc. as needed}- Update UI (
lib/main.dart): UseFutureBuilderto handle the asynchronous data fetching.
import 'package:flutter/material.dart';import 'api_service.dart';import 'product.dart';// Import ProductBox, ProductPage, RatingBox widgets from previous examples if needed
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Product REST App', theme: ThemeData(primarySwatch: Colors.blue), home: const ProductListPage(), ); }}
class ProductListPage extends StatefulWidget { const ProductListPage({Key? key}) : super(key: key);
@override _ProductListPageState createState() => _ProductListPageState();}
class _ProductListPageState extends State<ProductListPage> { late Future<List<Product>> _productsFuture; final ApiService _apiService = ApiService();
@override void initState() { super.initState(); _productsFuture = _apiService.fetchProducts(); }
void _refreshProducts() { setState(() { _productsFuture = _apiService.fetchProducts(); }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Products from API'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _refreshProducts, // Add a refresh button tooltip: 'Refresh', ) ], ), body: Center( child: FutureBuilder<List<Product>>( future: _productsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // While data is loading return const CircularProgressIndicator(); } else if (snapshot.hasError) { // If an error occurred return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Error: ${snapshot.error}'), const SizedBox(height: 10), ElevatedButton( onPressed: _refreshProducts, child: const Text('Retry'), ) ], ); } else if (snapshot.hasData) { // If data is successfully loaded final products = snapshot.data!; if (products.isEmpty) { return const Text('No products found.'); } return ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; // Replace with your actual ProductBox or ListTile widget return ListTile( // leading: Image.network('$_baseUrl/images/${product.image}'), // Example if serving images leading: Image.asset('assets/images/${product.image}', width: 50, errorBuilder: (c, e, s) => Icon(Icons.image, size: 50)), // Example using local assets title: Text(product.name), subtitle: Text(product.description), trailing: Text('$${product.price}'), onTap: () { // Navigate to detail page if needed // Navigator.push(context, MaterialPageRoute(builder: (context) => ProductPage(item: product))); }, ); }, ); } else { // Should not happen in typical FutureBuilder usage with a non-null future return const Text('No data available.'); } }, ), ), ); }}Run the app. Ensure your simple web server is running and accessible. The app will show a loading indicator, then display the list of products fetched from products.json, or an error message if the request fails.