Skip to Content
FlutterLezione 5 Accesso Ai Dati5.3 - Scrittura Dati con API REST (POST, PUT, DELETE)

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 lista
  • addTodo(todo) → fa POST

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

CodiceDescrizione
401Token mancante o non valido
403Accesso negato
404Todo non trovato
400Parametri mancanti o errati

Note

  • Il campo description nei todo è opzionale.
  • is_done è un booleano (true o false).
  • Il token JWT viene restituito al login o registrazione.

Esercizio pratico avanzato

  1. Crea un form per aggiungere un Todo autenticato
  2. Salva il token con SharedPreferences
  3. Integra con repository e bloc/provider
  4. Mostra errori reali (401, timeout)
  5. Aggiungi feedback visivo (SnackBar, loader)