JSON e REST API
- Comprendere il formato JSON e il suo ruolo nello scambio dati
- Effettuare chiamate HTTP (GET) in Flutter
- Convertire dati JSON in oggetti Dart (e viceversa)
- Visualizzare i dati ricevuti in un’interfaccia Flutter
Introduzione
Una delle funzionalità più importanti di un’app moderna è la comunicazione con servizi esterni. Flutter offre strumenti per:
- Effettuare chiamate HTTP verso server esterni (API REST)
- Ricevere e inviare dati in formato JSON
Molte applicazioni mobili oggi non funzionano solo localmente: si connettono a server remoti per caricare o salvare dati. In questa lezione vedremo come Flutter ci consente di interagire con queste API, ricevere informazioni, e gestirle all’interno della nostra app.
Cos’è JSON
JSON (JavaScript Object Notation) è un formato standard per rappresentare dati strutturati. È leggibile sia dagli umani che dai computer ed è largamente usato nelle comunicazioni tra frontend (come un’app Flutter) e backend (come un server web).
Struttura del JSON
Un oggetto JSON è una collezione di coppie chiave-valore:
{
"id": 1,
"title": "Fare la spesa",
"completed": false
}
- Le chiavi sono stringhe.
- I valori possono essere numeri, stringhe, booleani, array o altri oggetti.
Conversione in Flutter
Flutter fornisce il pacchetto dart:convert
per lavorare con JSON:
import 'dart:convert';
final stringaJson = '{"id":1,"title":"Fare la spesa","completed":false}';
final mappa = jsonDecode(stringaJson);
print(mappa['title']); // Fare la spesa
Per passare da un oggetto Dart a JSON:
final jsonString = jsonEncode({"id": 1, "title": "Comprare latte"});
Le API REST
Le API REST sono interfacce che permettono ai client (come un’app) di comunicare con un server usando il protocollo HTTP. Ogni operazione corrisponde a un verbo HTTP:
GET
: recupera datiPOST
: invia nuovi datiPUT
/PATCH
: aggiorna datiDELETE
: rimuove dati
In questa lezione ci concentreremo su GET
.
Chiamate HTTP in Flutter
Per eseguire chiamate HTTP usiamo il pacchetto http
. Va aggiunto nel file pubspec.yaml
:
dependencies:
http: ^0.13.6
Poi importiamo:
import 'package:http/http.dart' as http;
import 'dart:convert';
Permessi necessari (Android e iOS)
Accesso al server della macchina host durante lo sviluppo
Emulatore Android (AVD)
Usa l’indirizzo:
http://10.0.2.2
Questo IP è un alias speciale che l’emulatore Android usa per collegarsi alla macchina host.
Esempio:
final url = 'http://10.0.2.2:3000/todos';
Simulatore iOS
Puoi usare normalmente localhost
oppure 127.0.0.1
, perché il simulatore iOS gira sulla stessa rete del tuo Mac host.
Esempio:
final url = 'http://localhost:3000/todos';
Android
Per eseguire chiamate HTTP su Android, è necessario aggiungere il seguente permesso nel file android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET"/>
Dove inserirlo?
All’interno del tag <manifest>
ma fuori dal tag <application>
:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tua_app">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="tua_app"
android:icon="@mipmap/ic_launcher">
...
</application>
</manifest>
Questo permesso è necessario anche durante lo sviluppo su emulatore Android.
iOS
Su iOS non è necessario alcun permesso esplicito per chiamate HTTPS. Tuttavia, se stai usando un server locale o un URL non sicuro (HTTP), ad esempio http://localhost:3000
, devi configurare l’Info.plist
:
Nel file ios/Runner/Info.plist
, aggiungi:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Usa questa configurazione solo per sviluppo. In produzione, è consigliato usare solo HTTPS.
Esempio: Caricare dati da un’API
Usiamo l’endpoint di test: https://jsonplaceholder.typicode.com/todos
Future<void> fetchTodos() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/todos'),
);
if (response.statusCode == 200) {
final List decoded = jsonDecode(response.body);
print(decoded.length); // Stampa la lunghezza della lista ricevuta
} else {
throw Exception('Errore nel fetch');
}
}
Questo metodo recupera una lista di “todo” dal server remoto. La risposta è una stringa JSON che viene decodificata in una lista.
Modellare i dati: fromJson / toJson
Per gestire meglio i dati, è consigliabile creare una classe Dart che rappresenta ogni oggetto:
class Todo {
final int id;
final String title;
final bool completed;
Todo({required this.id, required this.title, required this.completed});
factory Todo.fromJson(Map<String, dynamic> json) {
return Todo(
id: json['id'],
title: json['title'],
completed: json['completed'],
);
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'completed': completed,
};
}
Con questa struttura possiamo convertire facilmente da e verso JSON.
Integrazione con l’interfaccia Flutter
Creiamo una schermata che mostra la lista dei Todo
ricevuti:
class TodoPage extends StatefulWidget {
const TodoPage({super.key});
@override
State<TodoPage> createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
List<Todo> todos = [];
@override
void initState() {
super.initState();
loadTodos();
}
Future<void> loadTodos() async {
final res = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/todos?_limit=10'),
);
if (res.statusCode == 200) {
final List jsonList = jsonDecode(res.body);
setState(() {
todos = jsonList.map((e) => Todo.fromJson(e)).toList();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo API')),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
subtitle: Text('Completato: ${todo.completed}'),
);
},
),
);
}
}
Questo codice mostra:
- come recuperare i dati da un server remoto
- come trasformarli in oggetti Dart
- come presentarli in una
ListView