diegoRodicio

View Categories

📋 Modificar listas

📋 Unha lista que pode cambiar

No Tema 3.2 aprendemos a mostrar listas utilizando ListView e ListView.builder.

Ata agora, os elementos das listas estaban definidos no código e non podiamos engadir ou eliminar elementos desde a interface.

Neste exemplo imos crear unha lista de alumnos que permita:

  • Mostrar os alumnos que temos gardados.
  • Engadir un alumno escribindo o seu nome.
  • Eliminar un alumno premendo unha icona.

Para conseguilo, gardaremos os nomes nunha lista dentro do State e utilizaremos setState() cada vez que a modifiquemos.

Lista de alumnado con opcións para engadir e eliminar nomes
Lista de alumnado con opcións para engadir e eliminar nomes

📝 Engadir e eliminar elementos dunha lista

En Dart podemos crear unha lista de nomes:

final _nomes = <String>['Xurxo', 'Iria'];

Neste caso, a lista contén dous alumnos: Xurxo e Iria.

Para engadir un novo elemento, utilizamos o método add():

_nomes.add('Roi');

Agora a lista contén tres alumnos: Xurxo, Iria e Roi.

Para eliminar un elemento, podemos utilizar removeAt(), indicando a súa posición:

_nomes.removeAt(1);

Esta instrución elimina o elemento da posición 1, é dicir, Iria.

Lembra que as posicións dunha lista comezan en 0:

Posición (índice)Nome
0Xurxo
1Iria
2Roi

Tamén podemos consultar cantos elementos ten unha lista mediante length:

_nomes.length

E comprobar se está baleira mediante isEmpty:

_nomes.isEmpty

💡 A palabra final impide substituír _nomes por outra lista, pero permite engadir ou eliminar elementos da lista existente.

➕ Engadir un alumno

Para engadir alumnos utilizaremos un TextField e un botón «Engadir».

O usuario escribirá un nome e, ao premer o botón, executarase o método _engadir():

void _engadir() {
  final nome = _controlador.text.trim();

  if (nome.isEmpty) return;

  setState(() {
    _nomes.add(nome);
  });

  _controlador.clear();
}

O funcionamento é o seguinte:

  1. Lemos o nome escrito no TextField mediante _controlador.text.
  2. Eliminamos os espazos dos extremos utilizando trim().
  3. Comprobamos se está baleiro con isEmpty. Se é así, return remata o método e non engadimos nada.
  4. Engadimos o nome á lista mediante _nomes.add(nome).
  5. Actualizamos a pantalla utilizando setState().
  6. Limpamos o campo mediante _controlador.clear() para poder escribir outro nome.

🔄 Por que necesitamos setState()?

Como vimos no Tema 3.3, setState() informa a Flutter de que cambiaron os datos que utiliza a interface e que debe reconstruíla.

Neste caso:

setState(() {
  _nomes.add(nome);
});

A instrución add() engade o novo alumno á lista, mentres que setState() permite que Flutter actualice a pantalla para mostrar o cambio.

É importante distinguir as dúas operacións: add() modifica os datos e setState() provoca a actualización da interface.

➖ Eliminar un alumno

Para eliminar un alumno necesitamos coñecer a súa posición na lista.

Por exemplo, podemos crear este método:

void _eliminar(int indice) {
  setState(() {
    _nomes.removeAt(indice);
  });
}

O parámetro indice indica a posición do alumno que queremos eliminar.

Se chamamos ao método con _eliminar(0), eliminaremos o primeiro alumno da lista. Se utilizamos _eliminar(1), eliminaremos o segundo.

Grazas a setState(), a pantalla actualizarase automaticamente para mostrar a lista sen ese alumno.

Con todo, non é recomendable eliminar un alumno inmediatamente ao premer a icona. Podemos evitar eliminacións accidentais preguntando primeiro ao usuario se realmente quere facelo.

💬 Confirmar a eliminación

Para pedir unha confirmación podemos utilizar un AlertDialog, unha pequena ventá que aparece sobre a pantalla e permite mostrar unha mensaxe con varios botóns.

Neste exemplo, cando o usuario prema a icona de eliminar, aparecerá unha pregunta con dúas opcións:

  • Cancelar: pecha o diálogo sen eliminar o alumno.
  • Eliminar: confirma que queremos eliminar o alumno.
Diálogo de confirmación antes de eliminar un alumno
Diálogo de confirmación antes de eliminar un alumno

Para abrir un diálogo utilizamos showDialog().

Por exemplo:

showDialog(
  context: context,
  builder: (contextoDialogo) => AlertDialog(
    title: const Text('Eliminar alumno?'),
    content: const Text('Queres eliminar este alumno?'),
    actions: [
      TextButton(
        onPressed: () => Navigator.pop(contextoDialogo, false),
        child: const Text('Cancelar'),
      ),
      TextButton(
        onPressed: () => Navigator.pop(contextoDialogo, true),
        child: const Text('Eliminar'),
      ),
    ],
  ),
);

Neste código:

  • showDialog() abre o diálogo.
  • builder constrúe o widget que queremos mostrar.
  • AlertDialog define o contido da ventá.
  • title establece o título.
  • content establece a mensaxe.
  • actions contén os botóns.

Para pechar o diálogo utilizamos Navigator.pop(), como vimos ao traballar coa navegación entre pantallas.

Neste caso, tamén devolvemos un valor:

Navigator.pop(contextoDialogo, false);

Pecha o diálogo devolvendo false, porque o usuario cancelou a operación.

Navigator.pop(contextoDialogo, true);

Pecha o diálogo devolvendo true, porque o usuario confirmou a eliminación.

💡 Utilizamos contextoDialogo para identificar o contexto do propio diálogo. Deste xeito, podemos pechalo desde os seus botóns.

⏳ Como esperamos a resposta do usuario?

Cando mostramos un diálogo, necesitamos esperar a que o usuario escolla unha opción antes de continuar coa eliminación.

Para iso utilizamos async e await:

  • async indica que un método pode realizar operacións asíncronas.
  • await permite esperar a que remate unha operación antes de continuar coas seguintes instrucións.

Por exemplo:

final confirmado = await showDialog<bool>(
  context: context,
  builder: (contextoDialogo) => AlertDialog(
    title: const Text('Eliminar alumno?'),
    content: const Text('Queres eliminar este alumno?'),
    actions: [
      TextButton(
        onPressed: () => Navigator.pop(contextoDialogo, false),
        child: const Text('Cancelar'),
      ),
      TextButton(
        onPressed: () => Navigator.pop(contextoDialogo, true),
        child: const Text('Eliminar'),
      ),
    ],
  ),
);

A instrución await espera a que se peche o diálogo. Cando isto sucede, gardamos a resposta na variable confirmado.

O resultado pode ser:

ValorSignificado
trueO usuario premeu «Eliminar».
falseO usuario premeu «Cancelar».
nullO diálogo pechouse sen escoller ningunha opción.

O tipo <bool> indica que esperamos unha resposta booleana (true ou false), aínda que tamén podemos recibir null se o diálogo se pecha sen responder.

🔍 Como comprobamos a resposta?

Unha vez pechado o diálogo, debemos comprobar se o usuario confirmou a eliminación.

Para iso utilizaremos:

if (!mounted || confirmado != true) return;

Esta condición comproba dúas cousas:

  • !mounted: comproba se o State do widget xa non forma parte da interface.
  • confirmado != true: comproba se o usuario non confirmou a eliminación.

O operador || significa OU, polo que se se cumpre algunha destas condicións, return remata o método e non eliminamos nada.

A propiedade mounted é importante porque, mentres esperamos a resposta do diálogo, podería suceder que a pantalla deixase de existir.

💡 Polo momento, abonda con saber que mounted evita intentar actualizar unha pantalla que xa non existe. Utilizámolo por seguridade antes de chamar a setState() despois dunha operación asíncrona.

Se a pantalla continúa activa e o usuario confirmou a eliminación, podemos eliminar o alumno mediante removeAt().

Como o método utiliza async e non devolve un resultado útil, declarámolo como Future<void>.

🧪 Exemplo completo: lista de alumnos

Agora imos reunir todo o que aprendemos nunha aplicación que permita engadir e eliminar alumnos.

O programa utiliza:

  • Un TextField para introducir o nome.
  • Un botón para engadir novos alumnos.
  • Un ListView.builder para mostrar a lista.
  • Unha icona para eliminar cada alumno.
  • Un AlertDialog para confirmar a eliminación.
Lista de alumnado con opcións para engadir e eliminar nomes
Lista de alumnado con opcións para engadir e eliminar nomes

🔗 DartPad:

🔍 Como se constrúe a lista?

A parte encargada de mostrar os alumnos é:

Expanded(
  child: _nomes.isEmpty
      ? const Center(child: Text('Non hai alumnos'))
      : ListView.builder(
          itemCount: _nomes.length,
          itemBuilder: (context, indice) {
            final nome = _nomes[indice];

            return ListTile(
              title: Text(nome),
              trailing: IconButton(
                tooltip: 'Eliminar $nome',
                icon: const Icon(Icons.delete),
                onPressed: () => _eliminar(indice),
              ),
            );
          },
        ),
),

Neste fragmento utilizamos varios elementos que xa coñecemos:

InstruciónPara que serve?
ExpandedPermite que a lista ocupe o espazo dispoñible dentro da Column.
_nomes.isEmptyComproba se a lista está baleira.
itemCount: _nomes.lengthIndica cantos alumnos hai na lista.
itemBuilderConstrúe as filas da lista segundo se necesitan.
_nomes[indice]Obtén o nome do alumno correspondente a esa posición.
ListTileMostra unha fila co nome e a icona de eliminar.
onPressed: () => _eliminar(indice)Executa o método para eliminar o alumno seleccionado.

Tamén utilizamos o operador ternario ? :, que permite escoller entre dúas opcións segundo se cumpra unha condición:

_nomes.isEmpty
    ? const Center(child: Text('Non hai alumnos'))
    : ListView.builder(...)

Se a lista está baleira, mostramos a mensaxe «Non hai alumnos». En caso contrario, mostramos o ListView.builder cos alumnos.

🔄 Que sucede cando modificamos a lista?

Cando engadimos ou eliminamos un alumno, chamamos a setState().

Flutter volve executar build() e actualiza a interface cos datos actuais.

Por exemplo, se inicialmente temos dous alumnos:

['Xurxo', 'Iria']

E engadimos «Roi»:

_nomes.add('Roi');

A lista pasa a conter tres alumnos:

['Xurxo', 'Iria', 'Roi']

Ao reconstruírse a interface, itemCount utiliza a nova lonxitude da lista e ListView.builder pode mostrar os tres alumnos.

💡 ListView.builder non engade nin elimina elementos por si mesmo. Limítase a construír as filas a partir dos datos que lle proporcionamos. Somos nós quen modificamos a lista mediante métodos como add(), removeAt() ou remove().

🧪 Comproba o funcionamento

Executa a aplicación e realiza estas probas:

  1. Engade un alumno: escribe «Roi» e preme «Engadir». Comproba que aparece na lista.
  2. Engade outro alumno: escribe «Uxía» e comproba que tamén aparece.
  3. Intenta engadir un nome baleiro: preme «Engadir» sen escribir nada. Non debería engadirse ningún elemento.
  4. Cancela unha eliminación: preme a icona de eliminar dun alumno e escolle «Cancelar». O alumno debe continuar na lista.
  5. Confirma unha eliminación: volve premer a icona e escolle «Eliminar». O alumno debe desaparecer.
  6. Elimina todos os alumnos: comproba que aparece a mensaxe «Non hai alumnos».

💡 Os datos desta aplicación só se gardan na memoria mentres se executa. Se reiniciamos a aplicación, a lista volverá conter os alumnos definidos inicialmente no código. No Tema 3.6 aprenderemos a gardar información para conservala entre execucións.

O desenvolvemento máis completo de diálogos e seleccións queda para o Tema 3.7.

📚 Consulta: