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:
POST
PUT
PATCH
DELETE
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 SharedPreferences
Possiamo 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
,LoggingService
in 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
/todos
richiedono 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
description
nei todo è opzionale. is_done
è un booleano (true
ofalse
).- 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)