flutter_accessing_rest_apis
Flutter - Accessing REST APIs
Section titled “Flutter - Accessing REST APIs”Most modern mobile applications need to interact with backend services to fetch or send data. REST (Representational State Transfer) APIs using JSON (JavaScript Object Notation) over HTTP/S are a common standard for this communication.
Flutter provides packages and built-in Dart features to easily consume REST APIs.
The http Package
Section titled “The http Package”The core package for making HTTP requests in Dart/Flutter is http. It’s a Future-based library leveraging Dart’s async/await capabilities.
-
Add the
httppackage dependency to yourpubspec.yaml: -
Run
flutter pub get. -
Import the package in your Dart file, often using an alias:
Making Requests
Section titled “Making Requests”The http package provides top-level functions for common HTTP methods:
http.get(Uri url, {Map<String, String>? headers}): Sends a GET request. ReturnsFuture<http.Response>.http.post(Uri url, {Map<String, String>? headers, Object? body, Encoding? encoding}): Sends a POST request.bodyis often a JSON-encoded string. ReturnsFuture<http.Response>.http.put(...): Sends a PUT request.http.patch(...): Sends a PATCH request.http.delete(...): Sends a DELETE request.http.head(...): Sends a HEAD request.
All these methods return a Future<http.Response>. The http.Response object contains:
statusCode: The HTTP status code (e.g., 200 for OK, 404 for Not Found, 500 for Server Error).body: The response body as a String.headers: A map of response headers.contentLength: The length of the response body in bytes.request: Information about the original request.
Example: Fetching Data (GET Request)
Section titled “Example: Fetching Data (GET Request)”Let’s fetch a list of sample ‘todos’ from the JSONPlaceholder API.
1. Define a Data Model
Section titled “1. Define a Data Model”Create a simple class to represent the data structure (e.g., lib/todo.dart). Adding a fromJson factory constructor simplifies parsing.
class Todo { final int userId; final int id; final String title; final bool completed;
const Todo({ required this.userId, required this.id, required this.title, required this.completed, });
// Factory constructor to create a Todo from JSON factory Todo.fromJson(Map<String, dynamic> json) { return Todo( userId: json['userId'] as int, id: json['id'] as int, title: json['title'] as String, completed: json['completed'] as bool, ); }}2. Create a Service Function
Section titled “2. Create a Service Function”Create a function to handle the API call and parsing.
import 'package:http/http.dart' as http;import 'dart:convert';import 'dart:async';import 'todo.dart'; // Import your Todo model
Future<List<Todo>> fetchTodos() async { // Define the API endpoint URL final url = Uri.parse('https://jsonplaceholder.typicode.com/todos');
try { // Make the GET request final response = await http.get(url);
// Check if the request was successful (status code 200) if (response.statusCode == 200) { // Decode the JSON response body (which is a String) // The result is a List<dynamic> because jsonDecode doesn't know the specific types List<dynamic> jsonResponse = jsonDecode(response.body);
// Map the dynamic list to a List<Todo> using the fromJson factory return jsonResponse.map((data) => Todo.fromJson(data)).toList(); } else { // If the server did not return a 200 OK response, // throw an exception. throw Exception('Failed to load todos. Status code: ${response.statusCode}'); } } catch (e) { // Handle potential network errors or JSON parsing errors print('Error fetching todos: $e'); throw Exception('Failed to load todos: $e'); }}3. Display Data in UI using FutureBuilder
Section titled “3. Display Data in UI using FutureBuilder”Use FutureBuilder to handle the asynchronous nature of the API call and display the data or loading/error states.
import 'package:flutter/material.dart';// Import fetchTodos function and Todo modelimport 'api_service.dart';import 'todo.dart';
class TodoListScreen extends StatefulWidget { const TodoListScreen({Key? key}) : super(key: key);
@override _TodoListScreenState createState() => _TodoListScreenState();}
class _TodoListScreenState extends State<TodoListScreen> { late Future<List<Todo>> futureTodos;
@override void initState() { super.initState(); // Call the fetch function when the widget is initialized futureTodos = fetchTodos(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Fetched Todos'), ), body: Center( child: FutureBuilder<List<Todo>>( future: futureTodos, // The Future<List<Todo>> to observe builder: (context, snapshot) { // Check the connection state if (snapshot.connectionState == ConnectionState.waiting) { // If waiting, show a loading indicator return const CircularProgressIndicator(); } else if (snapshot.hasError) { // If there's an error, display it return Text('Error: ${snapshot.error}'); } else if (snapshot.hasData) { // If data is available, display the list final todos = snapshot.data!; return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( leading: CircleAvatar(child: Text(todo.id.toString())), title: Text(todo.title), trailing: Icon( todo.completed ? Icons.check_box : Icons.check_box_outline_blank, color: todo.completed ? Colors.green : Colors.grey, ), ); }, ); } else { // If no data and no error (shouldn't happen with this API if successful) return const Text('No todos found.'); } }, ), ), ); }}
// --- Usage Example ---// void main() => runApp(MaterialApp(home: TodoListScreen()));Sending Data (POST Request)
Section titled “Sending Data (POST Request)”To send data (e.g., creating a new resource), use http.post.
Future<http.Response> createTodo(String title) async { final url = Uri.parse('https://jsonplaceholder.typicode.com/todos');
try { final response = await http.post( url, headers: <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', // Set content type }, // Encode the data to JSON format body: jsonEncode(<String, dynamic>{ 'title': title, 'userId': 1, // Example data 'completed': false, }), );
if (response.statusCode == 201) { // 201 Created is common for successful POST print('Todo created successfully: ${response.body}'); return response; } else { throw Exception('Failed to create todo. Status code: ${response.statusCode}'); } } catch (e) { print('Error creating todo: $e'); throw Exception('Failed to create todo: $e'); }}Error Handling and Best Practices
Section titled “Error Handling and Best Practices”- Always check
response.statusCode: Don’t assume a request was successful. Check for 2xx status codes for success, and handle 4xx (client errors) and 5xx (server errors) appropriately. - Use
try-catch: Network requests can fail for various reasons (no internet, DNS issues, timeouts). Wrap yourhttpcalls intry-catchblocks. - Handle JSON Decoding Errors:
jsonDecodecan throw aFormatExceptionif the response body is not valid JSON. Include this in your error handling. - Use Data Models: Parsing JSON into strongly-typed Dart objects makes your code safer and easier to work with. Consider packages like
json_serializableto automatefromJson/toJsongeneration for complex models. - Provide User Feedback: Show loading indicators (
CircularProgressIndicator) while fetching data and display clear error messages to the user. - Consider
http.Client: For making multiple requests to the same server, creating anhttp.Clientinstance can be more efficient as it can reuse TCP connections. Remember toclient.close()when done. - Alternative Packages: For more advanced features like interceptors, FormData support, cancellation, and better error handling, consider packages like
dio.
Accessing REST APIs is a core part of many Flutter apps. The http package provides a solid foundation, and using it with FutureBuilder and proper error handling allows you to integrate network data effectively into your UI.