
Questa parte del tutorial riprende quanto spiegato nell’esempio Fetch data contenuto nella documentazione ufficiale di Flutter.
Il nome di questo progetto è flutter_fetch_data.
Per prendere e inviare dati attraverso internet, abbiamo a disposizione il pacchetto http. Le informazioni contenute in questa pagina sono state utilizzate per questa parte del Flutter tutorial che riguarda la creazione di una pagina Home e per questa parte che spiega come creare una pagina di dettaglio.
Aggiungiamo l’ultima versione (l’ultima versione si trova qui https://pub.dev/packages/http/install) della dipendenza nel file pubspec.yaml sotto dependencies:
Svuotiamo il contenuto creato automaticamente da Android Studio del file main.dart e importiamo il pacchetto aggiundendo questa riga di codice:
import 'package:http/http.dart' as http;
Se non dovesse funzionare, proviamo ad aggiungere i permessi alle connessioni ad internet inserendo questo codice al file AndroidManifest.xml.
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
Per fare un test utilizzeremo i dati di test presi da https://jsonplaceholder.typicode.com/ di un semplice album. La versione base del codice da inserire nel metodo build() è la seguente ma più avanti creeremo un codice più utile, quindi questo può essere inserito nel build per fare un test ma verrà sostituito.
Future<http.Response> fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
Il metodo http.get() restituisce un oggetto Future che contiene la Response. Per approfondire come funziona la classe Future (https://api.flutter.dev/flutter/dart-async/Future-class.html) ma in generale la classe Future funziona con le richieste asincrone e il suo oggetto rappresenta il valore grezzo di risposta del Webservice mentre http.Response contiene i dati ricevuti da una connessione http che restituisce dati leggibili e che ha funzionato correttamente (statuscode = 200).
Il valore ricevuto va ora convertito in un oggetto Future<http.Response> così da rendere più semplice la fruizione dei dati contenuti da http.Response.
Creaimo una classe che chiameremo Album, in quanto i dati di test restituiti da jsonplaceholder costituiscono un album, che include un factory constructor che crea un Album dal json.
Inseriamo quindi questo codice in fondo al main.dart:
class Album {
final int userId;
final int id;
final String title;
Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
In questa parte della sezione, la documentazione ufficiale ci permette di aggiornare la funzione fetchAlbum() per restituire Future<Album> e cioè un oggetto Future (del quale abbiamo parlato nello step 2) di tipo Album.
a) Convertiamo la risposta che attualmente è un oggetto JSON Map con un dart:convert;
b) Controlliamo se il server ha risposto con un OK (quindi con uno status code di 200) allora convertiamo JSON Map in una classe Album usando il metodo fromJson();
c) Se invece il server risponde con un codice diverso da 200 (quindi diverso da OK) allora facciamo salire un’eccezione (exception).
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Ora possiamo prendere il dato e utilizzarlo come necessario.
Chiamiamo il metodo fetchAlbum() nel metodo initState() tenendo a mente che il metodo initState() viene chiamato solo una volta e quindi scaricherà i dati dal Webservice solo una volta.
Sostituiamo class _MyHomePageState extends State { con questa che segue
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
// ···
}
Per mostrare nell’interfaccia i dati ricevuti, utilizzeremo il widget FutureBuilder che semplifica il lavoro con le fonti di dati asincrone (come un Webservice). A questo dovremo passare il parametro Future con il quale vorremo lavorare (che in questo caso sarà l’oggetto Future restituito dalla funzione fetchAlbum()) e una funzione builder che dice a Flutter quando mostrare il dato ricevuto e che dipende dallo stato del Futer (loading/success/error).
Come configurato prima, fetchAlbum restituire solo valori true ed eleva una exception quando c’è un errore, come preso il caso dell’errore 404 e in questo caso lo snapshot.hasError sarà true, condizione che verrà utilizzata per mostrare un messaggio di errore.
FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
)
Il codice completo di questo esempio è a questo indirizzo: https://flutter.dev/docs/cookbook/networking/fetch-data#complete-example
Adesso che abbiamo spiegato le parti dell’esempio seguendo la documentazione ufficiale, vediamo come ricreare questo progetto integrandolo al codice automaticamente fornito da Android Studio quando viene creato un nuovo Flutter project. L’app automaticamente creata è quella con il bottone in basso a destra che incrementa il numero stampato al centro.
Partendo da qui, inseriamo i pacchetti da importare in alto e implementiamo tutte le modifiche necessarie.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Inseriamo di seguito la funzione fetchAlbum che abbiamo realizzato sopra
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
E a classe Album di cui abbiamo parlato sopra:
class Album {
final int userId;
final int id;
final String title;
Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
Lasciamo la riga del void main() senza alcuna modifica e sostituiamo il widget stateless disponibile (cioè class MyApp extends StatelessWidget) con questo stateful:
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
ed eliminiamo sia lo stateful widget già presente (class MyHomePage extends StatefulWidget nell’app creata automaticamente) che la sua classe State (_MyHomePageState) per inserire l’ultima parte:
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
Adesso l’app non presenta più il bottone dell’app creata automaticamente da Android Studio e stampa al centro il contenuto del Webservice https://jsonplaceholder.typicode.com/albums/1. Per fare un test diverso possiamo cambiare il numero dell’album variando di conseguenza l’URL, ad esempio impostando il numero 2 in questo modo https://jsonplaceholder.typicode.com/albums/2 e riavviando l’app vedremo una frase diversa (scaricata dal secondo album) al centro della pagina.