Aplicacions amb Flutter, Dart i Flame

Tutorial Flutter Flame Projectes   Recursos CITCEA
Exemples Dart Dades pràctiques     Inici

Fem una pantalla de detall

Si consultem les dades que obtenim, podem veure que hi ha més camps, a banda del nom i la professió. Anem a fer que, quan es piqui sobre una persona del llistat, s'obri una segona pantalla que mostri més informació.

Començarem per crear una pantalla, que podem anomenar PantDetalls i guardar-la com a pant_detalls.dart dins de la carpeta screens. Pot ser que més endavant aquesta pantalla tingui accions i, per tant, serà millor crear-la com a giny amb estat. Caldrà passar-li les dades de la persona a mostrar, que serà un objecte del tipus DadesDonaLlista. Per fer una primera prova, només hi posarem un text que mostri el nom de la persona.

pant_detalls.dart
import 'package:dones_destacades/data/dades_dona_llista.dart';
import 'package:flutter/material.dart';
class PantDetalls extends StatefulWidget {
  final DadesDonaLlista persona;
  const PantDetalls({super.key, required this.persona});

  @override
  State<PantDetalls> createState() => _PantDetallsState();
}

class _PantDetallsState extends State<PantDetalls> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text(widget.persona.nom),
    );
  }
}

Ara ens cal cridar aquest giny des de la pantalla principal. Allà tenim un mètode anomenat mostraElement, que és el que mostra cada un dels elements de la llista. Per a cada un es crea una columna; per tant, el més pràctic és fer que es canviï de pantalla cada cop que es pica sobre la columna. Així, es passarà a la segona pantalla tant si es pica sobre el nom com sobre la professió. Per tant, posarem la columna dins d'un detector d'accions.

...
  Padding mostraElement(DadesDonaLlista donaDeLaLlista) {
    var unescape = HtmlUnescape();
    return Padding(
      padding: const EdgeInsets.all(5.0),
      child: GestureDetector(
        onTap: () => Navigator.push(context, MaterialPageRoute(
            builder: (context) => PantDetalls(persona: donaDeLaLlista),
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(unescape.convert(donaDeLaLlista.nom), 
              style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
            Text(unescape.convert(donaDeLaLlista.professio), 
              style: TextStyle(fontSize: 13)),
          ]
        )
      )
    );
  }
}

Si ho provem, veurem que funciona però mostra el nom amb els caràcters especials codificats; aviat ho arreglarem. També ens queda el text mal situat, però això es pot millorar afegint la barra de l'aplicació, on podem mostrar el nom de la persona.

class _PantDetallsState extends State<PantDetalls> {
  @override
  Widget build(BuildContext context) {
    var unescape = HtmlUnescape();
    return Scaffold(
      appBar: AppBar(
        title: Text(unescape.convert(widget.persona.nom)),
      ),
      body: Text(unescape.convert(widget.persona.nom)),
    );
  }
}

Anem a mirar les dades que rebem per a una persona concreta:

    {
      "Id": "1826",
      "Ambit": "PS",
      "Nom": "Àngels Ferrer Sensat",
      "Nom casada": "",
      "Altre nom": "",
      "Professio": "Científica catalana",
      "Fet destacat 1": "Va ser innovadora en metodologies d'ensenyament"
    },

De moment, gestionem les dues que estan marcades. Podem modificar la classe DadesDonaLlista per obtenir les altres que ens interessin. Al mètode DadesDonaLlista hi afegirem la resta de camps:

class DadesDonaLlista {
  final String id;
  final String ambit;
  final String nom;
  final String casada;
  final String alies;
  final String professio;
  final String destacat;


  DadesDonaLlista({
    required this.id,
    required this.ambit,
    required this.nom,
    required this.casada,
    required this.alies,
    required this.professio,
    required this.destacat,
  });
  
  factory DadesDonaLlista.fromJson(Map<String, dynamic> dadesJson) {
    return DadesDonaLlista(
      id: dadesJson["Id"] ?? "",
      ambit: dadesJson["Ambit"] ?? "",
      nom: dadesJson["Nom"] ?? "",
      casada: dadesJson["Nom casada"] ?? "",
      alies: dadesJson["Altre nom"] ?? "",
      professio: dadesJson["Professio"] ?? "",
      destacat: dadesJson["Fet destacat 1"] ?? "",
    );
  }
}

Alguns dels camps poden estar buits; quan això passa, rebrem un nul. Per evitar problemes, hem posat un valor alternatiu que correspon a un text buit; per evitar problemes, ho hem fet amb tots els camps.

Ara anem a mostrar els resultats a la segona pantalla. Hem posat una columna, a la qual posem blocs de text amb una mica de format; per exemple, hem deixat una petita separació entre els noms i la professió. En el cas del nom de casada i l'àlies hem posat un text al davant per tal que s'entengui què són aquests dos camps. Ens hem saltat dos camps (id i ambit) perquè no aporten informació útil. Si ho provem, observarem que no queda gaire bé que apareguin aquestes línies si no hi ha noms per mostrar.

...
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(unescape.convert(widget.persona.nom), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          Text("Nom de casada: ${unescape.convert(widget.persona.casada)}", style: TextStyle(fontSize: 13)),
          Text("Àlies: ${unescape.convert(widget.persona.alies)}", style: TextStyle(fontSize: 13)),
          SizedBox(height: 5),
          Text(unescape.convert(widget.persona.professio), style: TextStyle(fontSize: 14)),
          Text(unescape.convert(widget.persona.destacat), style: TextStyle(fontSize: 13)),
        ],
      ),
...

Ho podem arreglar fàcilment amb una condició; però cal tenir en compte que, atès que estem fent una llista de ginys, només podem fer servir la sentència if en una sola línia (sense claus).

...
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(unescape.convert(widget.persona.nom), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          if(widget.persona.casada.isNotEmpty) 
            Text("Nom de casada: ${unescape.convert(widget.persona.casada)}", style: TextStyle(fontSize: 13)),
          if(widget.persona.alies.isNotEmpty) 
            Text("Àlies: ${unescape.convert(widget.persona.alies)}", style: TextStyle(fontSize: 13)),
          SizedBox(height: 5),
          Text(unescape.convert(widget.persona.professio), style: TextStyle(fontSize: 14)),
          Text(unescape.convert(widget.persona.destacat), style: TextStyle(fontSize: 13)),
        ],
      ),
...

Els diferents fitxers de l'aplicació, doncs, quedaran així:

main.dart
import 'package:dones_destacades/screens/pant_principal.dart';
import 'package:flutter/material.dart';
void main() {
  runApp(const MainApp());
}
class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: PantPrincipal()
    );
  }
}

pant_principal.dart
import 'package:dones_destacades/data/crida_llista_dones.dart';
import 'package:dones_destacades/data/dades_dona_llista.dart';
import 'package:dones_destacades/data/dades_rebudes_dones.dart';
import 'package:dones_destacades/screens/pant_detalls.dart';
import 'package:flutter/material.dart';
import 'package:html_unescape/html_unescape.dart';  // No ho posa automàticament
class PantPrincipal extends StatefulWidget {
  const PantPrincipal({super.key});

  @override
  State<PantPrincipal> createState() => _PantPrincipalState();
}

class _PantPrincipalState extends State<PantPrincipal> {
  Future<DadesRebudesDones?>? _dadesDones;
  CridaLlistaDones cridaLlistaDones = CridaLlistaDones();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Dones destacades"),),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextButton(
              onPressed: () {
                setState(() {
                  _dadesDones = cridaLlistaDones.demanarLlistaDones();
                });
              },
              child: Text("Dones catalanes"),
            ),
          ),
          mostraLlista()
        ],
      ),
    );
  }

  FutureBuilder<DadesRebudesDones?> mostraLlista() {
    return FutureBuilder(future: _dadesDones, builder: (context, snapshot){
      if(snapshot.connectionState == ConnectionState.waiting){
        return CircularProgressIndicator();
      } else if(snapshot.hasError){
        return Text("Error: ${snapshot.error}");
      } else if(snapshot.hasData){
        var llistaDones = snapshot.data?.llistaDadesDonaLlista;
        return Expanded(
          child: ListView.builder(
            itemCount: llistaDones?.length ?? 0,
            itemBuilder: (context, index) {
              if(llistaDones != null){
                return mostraElement(llistaDones[index]);
              } else {
                return Text("Error a la llista");
              }
            },
          ),
        );
      } else {
        return Text("No s'han trobat resultats");
      }
    });
  }

  Padding mostraElement(DadesDonaLlista donaDeLaLlista) {
    var unescape = HtmlUnescape();
    return Padding(
      padding: const EdgeInsets.all(5.0),
      child: GestureDetector(
        onTap: () => Navigator.push(context, MaterialPageRoute(
            builder: (context) => PantDetalls(persona: donaDeLaLlista),
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(unescape.convert(donaDeLaLlista.nom), 
              style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
            Text(unescape.convert(donaDeLaLlista.professio), 
              style: TextStyle(fontSize: 13)),
          ]
        )
      )
    );
  }
}

pant_detalls.dart
import 'package:dones_destacades/data/dades_dona_llista.dart';
import 'package:flutter/material.dart';
import 'package:html_unescape/html_unescape.dart';
class PantDetalls extends StatefulWidget {
  final DadesDonaLlista persona;
  const PantDetalls({super.key, required this.persona});

  @override
  State<PantDetalls> createState() => _PantDetallsState();
}

class _PantDetallsState extends State<PantDetalls> {
  @override
  Widget build(BuildContext context) {
    var unescape = HtmlUnescape();
    return Scaffold(
      appBar: AppBar(
        title: Text(unescape.convert(widget.persona.nom)),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(unescape.convert(widget.persona.nom),
            style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
          if(widget.persona.casada.isNotEmpty) 
            Text("Nom de casada: ${unescape.convert(widget.persona.casada)}",
              style: TextStyle(fontSize: 13)),
          if(widget.persona.alies.isNotEmpty) 
            Text("Àlies: ${unescape.convert(widget.persona.alies)}",
              style: TextStyle(fontSize: 13)),
          SizedBox(height: 5),
          Text(unescape.convert(widget.persona.professio), style: TextStyle(fontSize: 14)),
          Text(unescape.convert(widget.persona.destacat), style: TextStyle(fontSize: 13)),
        ],
      ),
    );
  }
}

crida_llista_dones.dart
import 'dart:convert';
import 'package:dones_destacades/data/dades_rebudes_dones.dart';
import 'package:http/http.dart' as http;  // No el troba automàticament, s'ha d'entrar a mà
class CridaLlistaDones{
  Future<DadesRebudesDones?> demanarLlistaDones() async{
    final llistaDonesRebuda = await 
      http.get(Uri.parse("https://recursos.citcea.upc.edu/dones/service.php?tipus=cat"));
    if(llistaDonesRebuda.statusCode == 200){
      var jsonDecodificat = jsonDecode(llistaDonesRebuda.body);
      DadesRebudesDones dadesRebudesDones = DadesRebudesDones.fromJson(jsonDecodificat);
      return dadesRebudesDones;
    } else {
      return null;
    }
  }
}

dades_rebudes_dones.dart
import 'package:dones_destacades/data/dades_dona_llista.dart';
class DadesRebudesDones {
  final String descrip;
  final List<DadesDonaLlista> llistaDadesDonaLlista;

  DadesRebudesDones({required this.descrip, required this.llistaDadesDonaLlista});

  factory DadesRebudesDones.fromJson(Map<String, dynamic> jsonRebut){
    var dadesJsonRebut = jsonRebut["dades"] as List;
    List<DadesDonaLlista> llistaDadesDona = 
      dadesJsonRebut.map((dona) => DadesDonaLlista.fromJson(dona)).toList();

    return DadesRebudesDones(
      descrip: jsonRebut["info"]["descrip"],
      llistaDadesDonaLlista: llistaDadesDona
    );
  }
}

dades_dona_llista.dart
class DadesDonaLlista {
  final String id;
  final String ambit;
  final String nom;
  final String casada;
  final String alies;
  final String professio;
  final String destacat;


  DadesDonaLlista({
    required this.id,
    required this.ambit,
    required this.nom,
    required this.casada,
    required this.alies,
    required this.professio,
    required this.destacat,
  });
  
  factory DadesDonaLlista.fromJson(Map<String, dynamic> dadesJson) {
    return DadesDonaLlista(
      id: dadesJson["Id"] ?? "",
      ambit: dadesJson["Ambit"] ?? "",
      nom: dadesJson["Nom"] ?? "",
      casada: dadesJson["Nom casada"] ?? "",
      alies: dadesJson["Altre nom"] ?? "",
      professio: dadesJson["Professio"] ?? "",
      destacat: dadesJson["Fet destacat 1"] ?? "",
    );
  }
}

 

 

 

 

 

 

 

 

 

 

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