Conociendo las listas en Dart
Tags: dart, list, tutorial, cleancode
Published: 2020-04-06 15:15:25

¡Buenas a todos! En este post conoceremos un poco más la clase List para trabajar de forma más sencilla y eficiente con ella.

# Single Level of Abstraction (SLA) :crystal_ball:

**SLA** o principio de **mismo nivel de abstracción** nos indica, que en un código limpio, no debe mezclar instrucciones de alto nivel con instrucciones de bajo nivel en la implementación de nuestra lógica. Esta norma genera código que es legible y entendible al no mezclar niveles de profundidad dentro de un mismo método o fragmento de código.

Podemos diferenciar entre estructuras de bajo nivel o alto nivel cuando nuestro código debe utilizar índices numéricos, bucles for y while, comprobación manual de propiedades, etc.

Estructuras de alto nivel pueden ser métodos descriptivos que cumplan una función específica como la búsqueda por nombre, la ordenación por un criterio o el filtrado de datos.

Un ejemplo claro de estructuras de bajo nivel es la implementación de una búsqueda por nombre de un usuario, en un Array, usando bucles con índices y redirección de flujo. En este caso en Java.

```java

 public User userByName(User[] array, String name) {

       User user = null;

        for (int i = 0; i < array.length; i++) {
             if(array[i].name == name){
                user = array[i];
                break;
            }
        }

        return user;
    }
```

En los lenguajes de alto nivel ya tenemos métodos, creados por los desarrolladores del lenguaje, para que podamos simplificar estos procesos y escribir todo nuestro código en un nivel alto de abstracción, sin tener que trabajar con estructuras de bajo nivel. Aquí tenemos una búsqueda, por nombre de usuario, en una lista usando estructuras de alto nivel, escrito en Dart.

```dart 
 User userByName(List<User> array, String name){
     return array.firstWhere((User user) => user.name == name);
  }
```

# Métodos útiles de Listas de Dart

Ahora que ya hemos visto porque utilizar funcionalidades del propio lenguaje que tener que codificarlo nosotros, vamos a ver qué métodos útiles nos da List en Dart.

## map

El método map nos devuelve un nuevo iterable, con el resultado que le asignemos en el *callback*, que le pasamos al método. En este caso, queremos una lista que nos indique: **Username: + nombre del usuario**

```dart 
List<String> nameOfUsers = users.map((user) => 'Username: ' + user.name).toList();

print(nameOfUsers); // [Username: Alfredo, Username: pepito, Username: Otro]
```

## where y firstWhere
Los métodos de búsqueda o filtro where y firstWhere nos devuelven un iterable o ítem, que cumpla con el criterio indicado en la *callback*.

```dart
var usersOlderThan18 = users.where((user) => user.age >= 18).toList();

var firstUserOlderThan18 = users.firstWhere((user) => user.age >= 18);
```

## contains
Este método nos devuelve un *boolean* indicando, si la lista contiene el mismo ítem que le pasamos por parámetro. Es importante recalcar que **debe ser la misma instancia**, no un ítem que sea igual.

Por ejemplo, este código nos devuelve *false*

```dart 
 List<User> users = [User(name: 'Alfredo'), User(name: 'Pepito')];
 
 print(users.contains(User(name: 'Alfredo'))) // false;
```

este código nos devuelve *true* al ser la misma instancia

```dart
var usuario = User(name: 'Usuario');

users.add(usuario);

print(users.contains(usuario)) // true;
```

## any
Nos devuelve un *boolean*, indicando si algún ítem cumple el criterio especificado en la *callback*.

```dart
users.any((user) => user.age > 18);
```

## remove, removeAt, removeWhere
Estos métodos nos sirven para eliminar ítems de nuestra lista, es importante recalcar que **muta** o modifica la lista original, algunos ejemplos de ello:

* **remove**: Nos permite eliminar el ítem pasándole la misma instancia por parámetro, como hemos visto anteriormente, debe ser **el mismo ítem**, no uno igual.

```dart
var userTemp = users[0];

users.remove(userTemp);
```

* **removeAt**: Nos permite eliminar el ítem sabiendo su posición, **sin necesidad de tener la instancia del mismo**.

```dart
users.removeAt(0);
```

* **removeWhere**: Este último nos permite eliminar el ítem sin necesidad de tener su instancia o conocer su posición. Eliminará todos los elementos que cumplan con la condición de la *callback*.

```dart
users.removeWhere((user) => user.name == 'Alfredo');
```

## sort

Este método nos permite ordenar las listas, para ello utiliza *Comparator* si el ítem lo contiene o podemos pasarle en la *callback* la función que va a utilizar para saber cómo ordenar los elementos.

Hay clases del lenguaje que ya contienen un comparador y puede ordenar las listas automáticamente, como por ejemplos los números.

```dart
List<int> nums = [13, 2, -11];
nums.sort();
print(nums);  // [-11, 2, 13]
```

Si no tenemos *Comparator* implementado debemos retornar un *integer* en la *callback* con la siguiente especificación:

* Sí se debe ordenar por encima, un valor positivo.
* Sí son iguales un 0.
* Sí es menor un valor negativo.

```dart
List<User> users = [User(name: 'Alfredo', age: 22), User(name: 'Pepito', age: 19), User(name: 'Otro', age: 25)];

users.sort((userA, userB) => userA.age >= userB.age ? 0 : 1);

print(users); // [Otro, Alfredo, Pepito]
```

Si queremos que estén ordenados de menor a mayor:

```dart

users.sort((userA, userB) => userA.age >= userB.age ? 1 : 0);

print(users); // [Pepito, Alfredo, Otro]
``` 

## indexWhere

Este método nos devuelve el indice del ítem que cumpla la condición del *callback*, es muy útil para buscar cuándo no tenemos la instancia del objeto y queremos su posición.

```dart
users.indexWhere((user) => user.name == 'Alfredo');
```

# Propiedades útiles

En la clase List tenemos propiedades interesantes como *reversed* que nos devuelve un *iterador* con el contenido de la lista en un orden inverso.

```dart

List <User> usersInverted = users.reversed.toList();

users // Alfredo, Pepito, Otro
usersInverted // Otro, Pepito, Alfredo

```

U otras como *isEmpty* o *isNotEmpty* que nos devuelve un *boolean*, indicando si la lista contiene algún elemento.

# :bulb: Tip: construir una lista inmutable

En Dart, tenemos el *keyword* **final** que nos permite indicar que una variable no va a cambiar de tipo, pero esto no nos asegura la **integridad** de una lista. Al añadir o quitar un elemento la lista sigue teniendo el mismo tipo aunque su contenido haya cambiado.

```dart
final List<User> users = [User(name: 'Alfredo', age: 22), User(name: 'Pepito', age: 19), User(name: 'Otro', age: 25)];
   
users.add(User(name: 'Nuevo', age: 17));

users // [Alfredo, Pepito, Otro, Nuevo];

```

Para evitar esto tenemos el constructor *List.unmodifiable* que restringe que nuestra lista no permita añadir o remover elementos.

```dart 
final List<User> users = List.unmodifiable([User(name: 'Alfredo', age: 22), User(name: 'Pepito', age: 19), User(name: 'Otro', age: 25)]);
   
users.add(User(name: 'Nuevo', age: 17)); // Uncaught Error: Unsupported operation: add

users.removeAt(0); // Uncaught Error: Unsupported operation: removeAt

```

# Resumen

Con estos pocos métodos y utilidades podremos trabajar de una forma más rápida y generando código sencillo de leer y mantener usando las listas.

En este [enlace](https://api.dart.dev/stable/2.7.2/dart-core/List-class.html) os dejo toda la documentación de la clase List en el momento de escribir el post.

Recordad que usando *extensions methods* introducidos en Dart 2.7 podemos implementar nuestros propios métodos en la clase List y así mejorar la simplicidad de nuestro código, os dejo este [enlace](https://dart.dev/guides/language/extension-methods) por si no conocéis las extensiones en Dart.

¡Un saludo y hasta la próxima!