És freqüent que una aplicació hagi d'enviar i rebre dades des d'internet i la forma més habitual és amb ordres GET. La crida es pot fer arran d'una acció de l'usuari (com prémer un botó) o pot ser genèrica per recopilar dades que seran necessàries per al funcionament de l'aplicació. Pel que fa a les dades rebudes, pot ser que es tractin a l'aplicació o que es mostrin directament a l'usuari.
Per obtenir dades d'internet, farem la crida amb la funció http.get. Per tal que aquesta funció estigui disponible, cal fer dues coses. Per un costat, hem d'anar al fitxer pubspec.yaml per dir-li que la volem fer servir. Cal posar-ho a dependencies, indicant quina és la versió que volem. És recomanable indicar la darrera versió; atès que quan comencem a escriure http ens ajuda a autocompletar, el normal és que ja ens doni la darrera versió. En el cas de la imatge, la darrera era la 1.5.0.

Un cop afegit, cal picar al botó per baixar el paquet (
).
Per un altre costat, cal importar el paquet http.dart; ja que no ho fa de manera automàtica.
import 'package:http/http.dart' as http; // No el troba automàticament, s'ha d'entrar a mà
Si es preveu que l'aplicació es pugui instal·lar en dispositius que tinguin versions antiques d'Android, cal donar permís d'accés a internet. Això ho farem anar al fitxer AndroidManifest.xml que es troba a la carpeta main de la carpeta src de la carpeta app de la carpeta android i afegir la línia del permís després de la de manifest i abans de la d'application:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
...
Atenció: Cal tenir en compte que l'absència d'aquest permís pot donar problemes a l'executar l'aplicació en el dispositiu, malgrat aquesta funcioni correctament en el simulador. Per assegurar que l'aplicació funcionarà en qualsevol dispositiu Android, convé posar aquesta línia sempre que s'hagn de fer connexions a internet.
A la classe crida_llista_dones, crearem el mètode demanarLlistaDones. Des que es faci la petició de llegir les dades fins que aquestes arribin pot passar un temps; o, fins i tot, poden no arribar mai. Per tant, aquest mètode no el podrem definir com els que hem fet servir fins ara. Per un costat, el resultat del mètode serà un futur (Future), un tipus especial d'estructura de dades que s'omple de manera diferida. Per un altre costat, s'ha declarat com a asíncron (async) per tal que la resta de l'aplicació pugui continuar funcionant mentre s'espera l'arribada de les dades. Aquest mètode retornarà un objecte de tipus DadesRebudesDones, el que hem creat a la classe anterior.
Obtindrem les dades del servidor amb la comanda http.get, a la qual se li ha de passar l'adreça en format URI. Per convertir l'adreça de l'enllaç a aquest format, fem servir la funció Uri.parse. El resultat no es pot assignar directament a la variable, perquè trigarà en arribat; per això afegim l'ordre await, per indicar que ha d'esperar el resultat. Per exemple:
class ClasseQueFaLaCrida{
Future<TipusDeDades> demanarDades() async{
final dadesRebudes = await
http.get(Uri.parse("https://servidor.domini.ext/ruta"));
}
}
La variable dadesRebudes no és directament la informació, sinó que conté tot un informe de com ha anat la comunicació. Un dels elements és statusCode que tindrà un codi numèric normalitzat que indica el resultat de les transmissions web. Normalment només ens interessa el cas que el codi sigui 200 (transmissió correcta); ja que en els altres casos no tenim dades.
Habitualment posarem una sentència if, que només tractarà les dades si la transmissió ha estat correcta. Si no ha estat correcta, tenim diverses opcions. Una d'elles seria crear una excepció:
class ClasseQueFaLaCrida{
Future<TipusDeDades> demanarDades() async{
final dadesRebudes = await
http.get(Uri.parse("https://servidor.domini.ext/ruta"));
if(dadesRebudes.statusCode == 200){
...
} else {
throw Exception("Alguna cosa ha fallat");
}
}
}
Però aquesta opció ens farà que l'aplicació es tanqui o es pengi si la transmissió no ha estat correcta. Per tant, potser és millor que es retorni alguna cosa. Si la comunicació ha estat correcta, l'element body de la variable dadesRebudes contindrà les dades.
La crida a la funció es farà en una acció; per exemple d'un polsador:
...
child: TextButton(
onPressed: () {
setState(() {
variable = ClasseQueFaLaCrida.demanarDades();
});
},
...
Podem trobar un exemple d'això en aquest apartat, que correspon a l'exemple de recuperar dades en format JSON.
Si volem carregar les dades a l'inici, convé fer-ho al més aviat possible; per tant, el millor és fer-ho a l'inicialitzar la pantalla principal. En principi, farem servir el mètode initState de la classe; però aquest mètode no admet futurs. Per això aquest mètode farà la crida a una funció separada. Fem servir també una variable per detectar (amb la funció setState quan les dades ja estan carregades.
class PantPrincipal extends StatefulWidget {
const PantPrincipal({super.key});
@override
State<PantPrincipal> createState() => _PantPrincipalState();
}
class _PantPrincipalState extends State<PantPrincipal> {
Map<String, String> _dadesCarregades = {};
ClasseQueFaLaCrida classeQueFaLaCrida = ClasseQueFaLaCrida();
bool _carregat = false;
@override
void initState() {
super.initState();
_carregarDades();
}
Future<void> _carregarDades() async {
final dades = await cridaLlistaAmbits.demanarLlistaAmbits();
setState(() {
_dadesCarregades = dades;
_carregat = true;
});
}
@override
Widget build(BuildContext context) {
...
Podem trobar un exemple d'això en aquest apartat, que correspon a l'exemple de recuperar dades en format JSON.
Per mostrar les dades a l'usuari, la forma més raonable és fer servir un constructor de futurs, que ens permet controlar si les dades han arribat correctament.
...
class _PantPrincipalState extends State<PantPrincipal> {
Future<DadesRebudes?>? variable;
ClasseQueFaLaCrida classeQueFaLaCrida = ClasseQueFaLaCrida();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ...,
body: ...
mostraResultat()
),
);
}
FutureBuilder<DadesRebudes?> mostraResultat() {
return FutureBuilder(future: variable, builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
} else if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
} else if(snapshot.hasData){
var rebut = snapshot.data?.campDeLlista;
return Expanded(
child: ListView.builder(
itemCount: rebut?.length ?? 0,
itemBuilder: (context, index) {
if(rebut != null){
return mostraElement(rebut[index]);
} else {
return Text("Error a la llista");
}
},
),
);
} else {
return Text("No s'han trobat resultats");
}
});
}
...
Podem trobar un exemple d'això en aquest apartat, que correspon a l'exemple de recuperar dades en format JSON.
El més habitual és que la resposta contingui dades que ens interessa tractar; però, a vegades, ens pot interessar tractar la resposta com a un text. Això té lògica, per exemple, quan la crida web serveix per enviar i, per tant, la resposta només ens diu si la transmissió s'ha fet correctament. La funció utf8.decode ens permet convertir la resposta a text.
import 'dart:convert';
...
Future<String> enviaDades(adre) async{
final resposta =
await http.get(Uri.parse(adre));
String decoded = utf8.decode(resposta.bodyBytes);
return decoded;
}
...

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.