DEV Community

Cover image for Widgets y FutureBuilder
Pablo L
Pablo L

Posted on • Updated on

Widgets y FutureBuilder

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 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 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 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 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 (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!!'.

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)));
  }
}
Enter fullscreen mode Exit fullscreen mode

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 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

...
...
    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
        }
      })
    );
...
...
Enter fullscreen mode Exit fullscreen mode

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.

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)));
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)