Scrittura Dati con API REST (POST, PUT, DELETE)
Obiettivi della Lezione
- Comprendere come inviare dati a un server tramite API REST
- Usare i metodi HTTP: POST, PUT, PATCH, DELETE
- Gestire header, body e codifica JSON
- Integrare il Repository Pattern e la separazione in servizi
- Gestire fallimenti di rete e scenari reali
- Implementare autenticazione con token
- Mostrare feedback all’utente (SnackBar, errori)
1. Introduzione
Nella lezione precedente abbiamo imparato a leggere dati da un’API (GET). Ora vedremo come inviarli, aggiornarli o eliminarli, usando:
POSTPUTPATCHDELETE
Useremo ancora l’API di test JSONPlaceholder , che simula risposte reali.
2. Concetti base delle richieste HTTP
Header Content-Type
headers: {'Content-Type': 'application/json'}Codifica del body JSON
body: jsonEncode({'title': 'Comprare latte'})3. Esempi CRUD con http
POST
Future<void> createTodo() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/todos'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': 'Comprare latte',
'completed': false,
'userId': 1,
}),
);
print(response.statusCode == 201 ? 'Creato' : 'Errore');
}PUT
Future<void> updateTodoPut(int id) async {
final response = await http.put(
Uri.parse('https://jsonplaceholder.typicode.com/todos/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'id': id,
'title': 'Aggiornato',
'completed': true,
'userId': 1,
}),
);
print(response.statusCode);
}PATCH
Future<void> updateTodoPatch(int id) async {
final response = await http.patch(
Uri.parse('https://jsonplaceholder.typicode.com/todos/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'completed': true}),
);
print(response.statusCode);
}DELETE
Future<void> deleteTodo(int id) async {
final response = await http.delete(
Uri.parse('https://jsonplaceholder.typicode.com/todos/$id'),
);
print(response.statusCode);
}Esempio con stato UI
Future<void> addTodoWithFeedback(BuildContext context) async {
try {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/todos'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': 'Nuovo task',
'completed': false,
'userId': 1,
}),
);
if (response.statusCode == 201) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Todo aggiunto con successo')),
);
} else {
throw Exception();
}
} catch (_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Errore nell'aggiunta del todo')),
);
}
}4. Repository Pattern
Il Repository rappresenta l’interfaccia verso la fonte dei dati (API REST, SQLite, Firebase, ecc.). Si occupa di recuperare, creare, aggiornare o cancellare i dati.
Esempi tipici:
fetchTodos()→ chiama API e restituisce listaaddTodo(todo)→ faPOST
Il repository non contiene logica di business, ma solo il collegamento ai dati.
class TodoRepository {
final String baseUrl = 'https://jsonplaceholder.typicode.com/todos';
Future<List<dynamic>> fetchTodos() async {
final res = await http.get(Uri.parse(baseUrl));
return res.statusCode == 200 ? jsonDecode(res.body) : throw Exception();
}
Future<void> addTodo(String title) async { ... }
Future<void> updateTodo(...) async { ... }
Future<void> deleteTodo(...) async { ... }
}5. Gestione degli errori
Una buona app deve gestire:
- Mancanza di connessione a internet
- Timeout
- Risposte inaspettate dal server
Ecco un esempio con try/catch e gestione di errori specifici:
import 'dart:io';
import 'dart:async';
Future<void> fetchWithRetry() async {
try {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/todos'))
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
print('Dati caricati correttamente');
} else {
print('Errore del server: ${response.statusCode}');
}
} on SocketException {
print('Nessuna connessione a internet');
} on TimeoutException {
print('Timeout della richiesta');
} catch (e) {
print('Errore generico: $e');
}
}Puoi associare questi errori a messaggi visivi per migliorare la UX.
6. Autenticazione API (Token-Based)
In molte API reali è necessario autenticarsi per accedere o modificare le risorse. Un meccanismo comune è l’autenticazione basata su token (es. JWT).
Esempio: Aggiunta del token all’header
Supponiamo che l’utente abbia effettuato il login e ottenuto un token da salvare in locale:
final token = 'eyJhbGciOi...'; // solitamente preso da SharedPreferencesPossiamo poi includere il token nella chiamata HTTP:
final response = await http.post(
Uri.parse('https://api.miosito.com/todos'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
},
body: jsonEncode({
'title': 'Nuovo task autenticato',
'completed': false
}),
);Gestione con SharedPreferences:
final prefs = await SharedPreferences.getInstance();
await prefs.setString('authToken', token);
final savedToken = prefs.getString('authToken');Repository con autenticazione
Nel nostro TodoRepository possiamo estendere i metodi per accettare un token:
class TodoRepository {
final String baseUrl = 'https://api.miosito.com/todos';
Future<void> addTodoAutenticato(String token, String title) async {
final response = await http.post(
Uri.parse(baseUrl),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
},
body: jsonEncode({
'title': title,
'completed': false
}),
);
if (response.statusCode != 201) {
throw Exception('Errore nell'inserimento autenticato');
}
}
}7. Services vs Repository
Repository
Gestisce solo l’accesso ai dati:
class TodoRepository {
Future<void> addTodo(String title) {...}
}Service
Un Service combina più chiamate del repository o esegue logica più ampia o orchestrata.
Esempi:
- Salva un
Todo, poi lo sincronizza su Firebase - Salva un dato localmente e lo invia a un server se c’è connessione
- Usa
SharedPreferences,Repository,LoggingServicein sequenza
Esempio pratico
class TodoService {
final TodoRepository repository;
final NetworkInfo networkInfo;
TodoService(this.repository, this.networkInfo);
Future<void> safeAddTodo(String title) async {
if (await networkInfo.isConnected()) {
await repository.addTodo(title);
} else {
// Salva localmente o mostra messaggio offline
}
}
}API - Autenticazione e Gestione Todo
Questa documentazione descrive le API disponibili per la gestione di autenticazione e lista Todo. Tutti gli endpoint /todos richiedono autenticazione con token JWT.
Autenticazione
POST /api/register
Registra un nuovo utente.
Request Body:
{
"email": "[email protected]",
"password": "securepassword"
}Response:
{
"token": "<jwt-token>"
}POST /api/login
Effettua il login con email e password.
Request Body:
{
"email": "[email protected]",
"password": "securepassword"
}Response:
{
"token": "<jwt-token>"
}Todos
Tutti i metodi
/todosrichiedono il token JWT nell’header:
Authorization: Bearer <token>
GET /api/todos
Restituisce tutti i todo dell’utente autenticato.
Response:
[
{
"id": 1,
"title": "Comprare il pane",
"description": "Dal panettiere",
"created_at": "2024-05-14T12:00:00.000Z",
"is_done": false
}
]POST /api/todos
Crea un nuovo todo.
Request Body:
{
"title": "Nuovo task",
"description": "Opzionale"
}Response:
{
"message": "Todo creato"
}PATCH /api/todos/:id
Aggiorna lo stato is_done di un todo.
Request Body:
{
"is_done": true
}Response:
{
"message": "Todo aggiornato"
}DELETE /api/todos/:id
Elimina un todo per ID.
Response:
{
"message": "Todo eliminato"
}⚠️ Codici di Errore
| Codice | Descrizione |
|---|---|
| 401 | Token mancante o non valido |
| 403 | Accesso negato |
| 404 | Todo non trovato |
| 400 | Parametri mancanti o errati |
Note
- Il campo
descriptionnei todo è opzionale. is_doneè un booleano (trueofalse).- Il token JWT viene restituito al login o registrazione.
Esercizio pratico avanzato
- Crea un form per aggiungere un Todo autenticato
- Salva il token con SharedPreferences
- Integra con repository e bloc/provider
- Mostra errori reali (
401, timeout) - Aggiungi feedback visivo (SnackBar, loader)