ListView en Flutter
Este es el tercer tutorial de las serie Fundamentos de Flutter donde aprendemos a utilizar el ListView en diferentes entornos, con datos por defecto, con datos desde una lista estática y obteniendo los datos desde un archivo externo.
Índice
- Qué es un ListView
- Llenando un ListView desde una lista
- Llenando un ListView desde un Json
- De String a Icon
- Tu turno
- El código
1. Qué es un ListView
Un ListView es un widget de Flutter que nos permite visualizar en forma de lista un conjunto de widgets dentro de él. Existen dos tipos de ListViews el primero es el listview.builder
el cual es el indicado para renderizar muchos elementos en la pantalla ya que se crean conforme se va haciendo scroll en la pantalla. El segundo es el listview
normal que renderiza todos los elementos desde el inicio, por lo que si la cantidad de elementos en su interior es demasiado grande este demorará en cargar. En este tutorial aprenderemos a crear un listview normal.
Un ListView solo funciona dentro de un Scaffold
. Así que podemos colocar el ListView dentro del body de un Scaffold. Sin embargo, para evitar colocar demasiado código dentro del Scaffold y sea ilegible usamos una función que nos devuelva un ListView y solo referenciamos a esa función dentro del body. A continuación veamos el creación de un ListView.
ListView _milista() {
return ListView(
children: <Widget>[
// ListTile es un widget que funciona como un bloque del ListView
ListTile(
// Tiene muchas propiedades que nos ayudan a ordenar la información.
// Titulo
title: Text('Primero'),
// Subtitulo
subtitle: Text('Este es el primer Tile'),
// Icono inicial del bloque
leading: Icon(Icons.add),
// Funcion que se ejecuta al hacer click en él
onTap: () {},
),
ListTile(
title: Text('Primero'),
onTap: () {},
)
],
);
}
Una propiedad también de importante es el pading
y este nos permite colocar un padding a nuestro ListView. Sin embargo, el tipo de dato que acepta esta propiedad es algo especial. Esta propiedad acepta un EdgeInsetsGeometry
que a su vez es una clase padre de EdgeInsets
. Así que podemos usar este último en la propiedad. Además podemos agregar el pading a todos lo lados como en el siguiente primer ejemplo o de un tamaño definido para la horizontal y vertical, como en el segundo ejemplo.
// Pading a todos los lados
padding: EdgeInsets.all(10),
// Pading exacto a los lados verticales y horizontales
padding: EdgeInsets.symmetric(horizontal: 23.0, vertical: 10.0),
Esa es la forma más fácil de crear un listView con elementos estáticos, pero no siempre se tiene elementos estáticos así que a continuación crearemos un listView a partir de una lista o arreglo.
2. Llenando un ListView desde una lista
En el código anterior vimos que el children de la ListView acepta una lista de widgets <Widget>[]
. Aprovecharemos eso y crearemos un método que nos devuelva una lista de Widgets que serán llenados recorriendo mediante un for
nuestra lista de string previamente creada.
class HomePage extends StatelessWidget {
final opciones = ['Primero', 'Segundo', 'Tercedddro'];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hola')),
body: ListView(
children: crearItems(),
),
);
}
List<Widget> crearItems() {
final lista = List<Widget>();
for (String opt in opciones) {
final tempWidget = ListTile(title: Text(opt));
lista.add(tempWidget);
// Divider es un widget que crea una division entre cada elemento de la lista.
// Agregamos un Divider debajo de cada elemento de la lista
lista.add(Divider());
}
return lista;
}
}
Acabamos de crear una lista a partir de una Lista. Ahora usando tu conocimientos del anterior tutorial ya puedes crear un botón que agregue elementos a la lista de tal forma que la lista aumente automáticamente. No te olvides que para esto la clase debe ser del tipo StatefulWidget
.
final List<Widget> lista = [];
List<Widget> lista = [];
List<Widget> lista = new List<Widget>();
List<Widget> lista = List<Widget>();
var lista = new List<Widget>();
var lista = List<Widget>();
final lista = new List<Widget>();
final lista = List<Widget>();
Otra forma para agregar varios elementos a la lista sin repetir código es usando el método de cascada de la siguiente forma
lista..add(tempWidget)..add(Divider());
// Es igual a usar
lista.add(tempWidget);
lista.add(Divider());
Otro método aún más elegante de llenar una lista es usando el método forEach
como en el siguiente ejemplo.
List<Widget> _crearItems() {
final lista = new List<Widget>();
opciones.forEach((element) {
final tempWidget = ListTile(
title: Text(element),
);
lista.add(tempWidget);
});
return lista;
}
Otra forma aún más simplificada para llenar un ListView es usando el método map
. Este método devuelve un Iterable
con los elementos que retornamos en la función flecha. Por ese motivo debemos usar el método toList()
para convertirlo en una lista de Widgets.
List<Widget> _crearItems(){
return opciones.map((item) =>
ListTile(title: Text(item))
).toList();
}
3. Llenando un ListView desde un Json
Para usar un archivo externo lo primero que tenemos que hacer es declararlo dentro del archivo pubspec.yaml
. Dentro encontraremos la sección assets comentada. Solo debemos quitar el símbolo #
de la parte delantera y dejarlo de la siguiente forma.
assets:
- data/menu.json
En nuestro caso trabajaremos con un archivo menu.json
que colocaremos dentro de la carpeta data
y contiene el siguiente código.
{
"menu": [
{
"icono": "add_alert",
"texto": "Primero :D"
},
{
"icono": "accessibility",
"texto": "Segundo :3"
},
{
"icono": "folder_open",
"texto": "Tercero :p"
}
]
}
Lo que haremos es llenar un ListView a partir de ese archivo .json
. Para esto crearemos un archivo llamado lib/src/providers/menu_provider.dart
donde colocaremos la clase que nos permitirá obtener una lista a partir del archivo .json
.
Para esto usaremos el método rootBundle
del paquete flutter/services.dart
. Usando su método loadString()
obtendremos el contenido de un archivo externo en este caso nuestro archivo json. Sin embargo, este método devuelve un Future<String>
y recordemos que un Future es como una promesa, algo que esperamos recibir pero aún no tenemos así que es adecuado para usar el async-await
para recibirlo.
import 'dart:convert';
import 'package:flutter/services.dart';
class MenuProvider {
// Creamos la lista que recibiá el objeto del json
// como es un objeto usmos dynamic
List<dynamic> opciones = [];
// Creamos una función que devuelve un Future
// El Futuro es una promesa por lo que podemos usar async-await
Future<List<dynamic>> cargarData() async {
// Obtenemos el contenido del archivo usando loadString
final respuesta = await rootBundle.loadString('data/menu.json');
// Usamos json.decode para convertir el contenido a un map
Map<String, dynamic> dataMap = json.decode(respuesta);
// Usamos el key 'menu' del map para optener una lista de objetos
opciones = dataMap['menu'];
// Retornamos la lista
return opciones;
}
}
// creamos el objeto, el cual lo obtendremos desde otro archivo
final menuProvider = new MenuProvider();
Existe un Widget que se construye a si mismo a partir de la ultima instantánea (snapshot) que viene desde un Future. Nos referimos a un FutureBuilder. Básicamente este widget construye otro widget a partir de un Future. Así que utilizaremos esto para crear nuestro ListView.
Widget listView() {
return FutureBuilder(
// A esta propiedad le damos el future
future: menuProvider.cargarDatos(),
// A esta propiedad le damos los datos iniciales mientras carga el Future
initialData: [],
// Esta propiedad lo explicamos debajo
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
return ListView(
children: listaItems(snapshot.data),
);
});
}
La propiedad builder
contiene una función que devuelve el Widget que se creará a partir del Future proporcionado a la propiedad future
. Esta función tiene dos parámetros, el primero es el context
que contiene información sobre nuestra aplicación en general y el segundo es elsnapshot
, que es la última instantánea de los datos que trajimos mediante el Future.
Así que usaremos esta instantánea para retornar un ListView
cuya propiedad children será creada mediante una función que tenga como parámetro el snapshot.data
, que es el objeto con los datos obtenidos del Future a partir del menu.json
. Entonces pasemos ahora a crear la lista de Widgets que necesita el ListView.
List<Widget> listaItems(List<dynamic> data) {
// Creamos la lista de Widgets que necesitará el children del ListView
final List<Widget> opciones = [];
// Usamos forEach para llenar los elementos de la lista
data.forEach((element) {
final widgetTemp = ListTile(
title: Text(element['texto']),
);
opciones.add(widgetTemp);
});
// Retornamos la lista de Widgets
return opciones;
}
Listo!! como ves es una especie de cadena que se forma con las diferentes funciones. Comienza con la creación del ListView que lo hace mediante un FutureBuilder que necesita del Future con los datos del menu.json
para retornar mediante la su propiedad builder
un ListView que se crea a partir del Future. Entonces creamos otro método que nos devuelva la lista de widgets para nuestro ListView.
4. De String a Icon
Para esto crearemos una carpeta utils
dentro del cual crearemos un archivo llamado string_to_icon.dart
donde primero crearemos un map de los iconos que utilizaremos.
// IconData es clase padre de Icons
final icons = <String, IconData>{
'add_alert' : Icons.add_alert,
'accessibility' : Icons.accessibility,
'folder_open' : Icons.folder_open
};
Ahora crearemos una función que reciba como parámetro un string y devuelva un icono leyendo el map que creamos anteriormente. Así que solo servirá para los iconos mapeados en nuestro map.
Icon getIcon(String nombreIcono){
return Icon(icons[nombreIcono]);
}
5. Tu turno
Usando estos conceptos que acabamos de aprender te animo a realizar el siguiente proyecto. Una aplicación en Flutter donde muestres un ListView con los datos e icono obtenidos a partir de un archivo json, como se muestra a continuación.
Cuando quieras puedes ver el código del proyecto terminado. Pero ándale inténtalo tu mismo!!
6. El código
El código de este proyecto está en el siguiente repositorio de GitHub: github.com/andygeek/list_app_flutter