Widgets y FutureBuilder
Tags: flutter
Published: 2019-10-26 16:52:30

###Introducción###
Se nos habrá presentado seguramente el caso en el que tenemos un Widget el cual necesita datos para su construcción pero estos datos no puede ser adquiridos inmediatamente sino que van a demorarse más o menos tiempo su obtención. Una lectura de un fichero, consultas a base de datos, un request a una página de internet...en fin operaciones asíncronas que pueden demorarse.

Obviamente hasta que no tengamos el contenido que necesita el Widget no deberíamos presentarlo pero...y si este contenido se demora como debemos actuar?

Flutter en su API nos provee de un Widget llamado [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) que nos permite lidiar con estos problemillas "asíncronos" de una modo fácil.

###FutureBuilder###
Para un funcionamiento mínimo FutureBuilder necesita en su constructor dos parámetros.

1. future 
2. builder

El parámetro future es una instancia [Future<T>](https://api.flutter.dev/flutter/dart-async/Future-class.html) con el valor necesitado por nuestro Widget.

El parámetro *builder* es un método que debemos sobreescribir que tiene dos parámetros *context* y *snapshot*. El segundo parámetro es el relevante. Es un representante de la clase [AsyncSnapshot](https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html) que contiene métodos de tipo bool *hasData*, *hasError*...útiles porque nos informan del estado de la operación asíncrona para que podamos reaccionar.

Lo vemos con un ejemplo. Simplemente es un [Text](https://api.flutter.dev/flutter/widgets/Text-class.html) que aparecerá en nuestra pantalla a los cinco segundos de iniciar la aplicación.

Cómo parámetro en *future* le suministramos un objeto Future<String> (devuelto por el método *getFutureData*) que será el contenido del texto.

En el método asíncrono *getFutureData* deliberadamente introducimos un retardo de cinco segundos en la obtención del literal 'Hola!!'. 

```dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }

}

class MyAppState extends State<MyApp>{
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: getFutureData(),
      builder: ((context, snapshot){
        if(snapshot.hasData){
          return _buildTextWidget(snapshot.data);
        }else{
          return CircularProgressIndicator(strokeWidth: 10,);
        }
      })
    );
  }

  Future<String> getFutureData() async{
    return await Future.delayed(Duration(seconds: 5), () {
      return 'Hola!!!';
    });
  }

  Widget _buildTextWidget(String data){
    return Center(child: Directionality(textDirection: TextDirection.ltr,child:Text(data,textScaleFactor: 3)));
  }
}
```

Nuestro *builder* es llamado por el sistema con los distintos estados de la operación asíncrona. 

Nosotros simplemente cuando no tenemos datos presentamos un [indicador de progreso circular](https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html) y cuando tenemos datos efectivamente presentamos el texto.

En un contexto de producción deberíamos tener en cuenta el estado de error en nuestro *builder*


```dart
...
...
    return FutureBuilder(
      future: getFutureData(),
      builder: ((context, snapshot){
        if(snapshot.hasData){
          //TODO Widget normal
        }if(snapshot.hasError){
          //TODO Widget de error
        }else{
          //TODO Widget de espera
        }
      })
    );
...
...
```

###FutureBuilder con datos iniciales###
El código mostrado es de lo más básico para una utilización correcta de FutureBuilder pero puede que querramos mostrar siempre el mismo Widget y no otro alternativo durante la demora como en el ejemplo. 

En este caso podemos usar el parámetro *initialData* 

Hemos cambiado el código anterior eliminando del builder la opción de mostrar el Widget de progreso y añadiendo el parámetro *initialData* con un literal que será mostrado cuando los datos aún no han llegado.

```dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }

}

class MyAppState extends State<MyApp>{
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: getFutureData(),
      initialData: "Cargando...",
      builder: ((context, snapshot){
        if(snapshot.hasData){
          return _buildTextWidget(snapshot.data);
        }
      })
    );
  }

  Future<String> getFutureData() async{
    return await Future.delayed(Duration(seconds: 5), () {
      return 'Hola!!!';
    });
  }

  Widget _buildTextWidget(String data){
    return Center(child: Directionality(textDirection: TextDirection.ltr,child:Text(data,textScaleFactor: 3)));
  }
}
```