Decodificando FutureBuilder

CarlosMillan
· 17 min read

Hola amigos, en esta día voy a dejar una traducción que hice del artículo escrito por Greg Perry sobre FutureBuilder, el cual puedes consultar el post en inglés aquí.

Qué es el widget FutureBuilder?

En pocas palabras, es una forma elegante de que su aplicación “espere” para que se complete una operación asíncrona antes de continuar. En muchos casos, se utiliza para crear la “pantalla de inicio” de la aplicación al crear un widget basado en la última “condición” de un objeto Futuro específico. Específicamente, construyendo y probablemente luego mostrando un widget particular dependiendo de si la operación asincrónica asociada con un objeto Futuro específico se completa o no. Además, una vez completado, construya y luego muestre un widget particular dependiendo del valor resultante representado por ese objeto Futuro. En el caso del escenario de “pantalla de inicio”, hasta que se complete la operación asíncrona del objeto Futuro, la aplicación generalmente mostrará una pantalla de “espera” o “carga”.

Entonces, ¿qué es el widget de FutureBuilder? Como sucede, el widget FutureBuilder es un StatefulWidget. En este artículo, revisaremos cómo funciona.

clase FutureBuilder a partir del 3 de abril de 2019

Como siempre, prefiero usar capturas de pantalla en lugar de códigos para mostrar el código en mis artículos. Los encuentro más fáciles de trabajar y más fáciles de leer. Sin embargo, puede hacer clic/tap en ellos para ver el código como un gist o en Github. Irónicamente, es mejor leer este artículo sobre el desarrollo móvil en su computadora que en su teléfono. Además, programamos principalmente en nuestras computadoras; No en nuestros teléfonos. Por ahora.

Vamos a empezar.

Aprender mediante ejemplos

Usaremos un ejemplo del Cookbook de Flutter, Recolectando datos desde internet, para demostrar un FutureBuilder en acción. A continuación se muestra una captura de pantalla de una parte de ese ejemplo que muestra un StatelessWidget y cómo su parámetro, publicación, se proporciona a un widget de FutureBuilder como un objeto Future instanciado.

Fetch and Display the data

La documentación de la clase FutureBuilder <T> enfatiza de inmediato que el futuro especificado debe obtenerse con anterioridad ya instanciado. No debe ser creado correctamente con FutureBuilder, cuando se llama a FutureBuilder y se crea una instancia. De lo contrario, ese objeto Futuro se creará una instancia con el FutureBuilder una y otra vez con cada nueva llamada a la función build () que los contiene, en algunos casos, posiblemente no permitiendo que se complete la operación asíncrona.

clase FutureBuilder a partir del 3 de abril de 2019

Un futuro en Genéricos

Al mirar arriba en la primera línea de la clase FutureBuilder, vemos que los tipos de datos genéricos se utilizan con su capital “T” y su notación <…>. Se utiliza para indicar qué “tipo de datos” se devolvió de la operación asíncrona. En el ejemplo de Cookbook, el “tipo de datos” es una clase de tipo, Publicación. Tenga en cuenta que, utilizando un FutureBuilder, el tipo podría ser cualquiera de los tipos incorporados ofrecidos por Flutter también.

Cómo va esto

Entonces, cómo irá esto: en la captura de pantalla del ejemplo anterior, se ve que el widget FutureBuilder se está llamando como el argumento “secundario” de un widget del Centro. Hagamos esto ahora en “cámara lenta” y veamos qué sucede después de que se llame a FutureBuilder en este momento. Lo que normalmente tomaría milisegundos, lo pasaremos.

Está todo en El Init

Recuerde, el FutureBuilder es un StatefulWidget. Por lo tanto, pronto se llama a la función initState() del objeto State que acompaña a StatefulWidget. Es allí donde las cosas realmente comienzan a rodar. Puede ver en la captura de pantalla siguiente, un objeto de tipo, AsyncSnapshot, se crea una instancia en la función, initState().

iniState () en FutureBuilder a partir del 3 de abril de 2019

Observe lo que se pasa al constructor AsyncSnapshot: un tipo enumerado con el valor of, ConnectionState.none y el valor del parámetro initalData de FutureBuilder, si corresponde. El tipo enumerado, ConnectionState, es utilizado tanto por el widget FutureBuilder como por el widget StreamBuilder para transmitir el progreso de sus respetables operaciones asíncronas. Hablaremos más sobre esto, pero por ahora, ¿de qué se trata este ‘widget.initialData‘?

Otras historias por Greg Perry

Dar el valor de los datos

A continuación, se muestra una captura de pantalla de un ejemplo diferente en el que FutureBuilder recibe un valor de “datos iniciales”, no una clase de tipo, Publicación, sino un tipo de datos booleanos, falso. Es ese valor que luego se pasa al constructor AsyncSnapshot en la función, initState (), que se muestra arriba como, widget.initialData.

En nuestro ejemplo de libro de cocina, no se proporciona dicho valor inicial, por lo que el valor del parámetro initialData se establecería en nulo. Ese parámetro es, de hecho, una propiedad de la clase, FutureBuilder. Implica el tipo de datos Generics, T, y así es capaz, en este caso, de almacenar un objeto de clase de tipo, Post.

La función FutureBuilder() a partir del 3 de abril de 2019

Sin embargo, si se pasa un parámetro de este tipo, FutureBuilder probablemente construirá un widget basado en ese valor. Ese widget se mostrará hasta que la operación asíncrona asociada se complete y proporcione su valor resultante al objeto Future especificado. ¿Estás siguiendo hasta ahora? No te preocupes, pasaré por esto otra vez. Vamos en “cámara lenta”, ¿recuerdas?

El Future Tiene Clase

Nuevamente, vuelva a mirar el ejemplo del Libro de cocina, el objeto Future específico es una clase de tipo, Publicación, y es proporcionado por el método, fetchPost(). Este método se llama de inmediato en la función main() y se suministra como un parámetro a StatelessWidget, MyApp.

El ejemplo completo de Abril 03, 2019

En la captura de pantalla a continuación, puede ver el método, fetchPost(), devuelve un objeto de tipo, Future<Post>. Es en este método, donde se llama un sitio web.

El Ejemplo Complo de Abril 03, 2019

El sitio web ha sido llamado para buscar datos, y eso lleva tiempo. Por lo tanto, el ejemplo del Cookbook ha implementado un FutureBuilder — para “esperar” a que se complete la recuperación de datos. El comando, aguardar, cuando se encuentre, hará que la ruta de ejecución se retire del método en ese punto, devolviendo un objeto Futuro “incompleto”. Luego, la aplicación continúa llamando al FutureBuilder, que recibe este objeto Future “incompleto” como su objeto Future especificado.

Al principio, un objeto Futuro “incompleto”

Comienza con un Build

Como cualquier StatefulWidget, cuando se llama por primera vez al FutureBuilder, el objeto State que lo acompaña ejecutará primero su función initState(). Poco después, se llama por primera vez a la función build() del objeto State. La siguiente captura de pantalla muestra la función build().

Función FutureBuilder’s build() de Abr 3, 2019

Tenga en cuenta que la función build(), a su vez, llama a la propiedad ‘builder’ de StatefulWidget. El StatefulWidget que es el FutureBuilder. La siguiente captura de pantalla que muestra el ejemplo del Cookbook en el que se definió por primera vez el widget FutureBuilder revela que esa propiedad es un parámetro con nombre al que se le asigna un método anónimo. Puede ver que ese método toma un objeto BuildContext y un objeto AsyncSnapshot como parámetros.

Ejemplo Completo de Abril 03, 2019

Y así, cada vez que se ejecute la función build () de este StatefulWidget, se ejecutará este método anónimo. Eso significa, además, que con cada llamada subsiguiente de la función setState () del objeto State, se ejecutará este método anónimo. Seguir hasta ahora?

Circular Progress al principio

Y así, con esta primera compilación del árbol de widgets, aparece un ‘cargando spinner ‘en el centro de la pantalla. Ahora, ¿por qué es eso? Recuerde, un objeto de tipo Futuro “incompleto”, Post, se había pasado a FutureBuilder al comienzo de todo esto, y cuando se ejecuta la función build() de FutureBuilder, se ejecuta ese método anónimo.

Como consecuencia, el valor de, snapshot.hasData, se establecerá en falso. Esto se debe a que no se proporcionó un “datos iniciales” como parámetro y el objeto Future está “incompleto”. Posteriormente, como no hubo ningún error (snapshot.hasError), se llama al widget CircularProgressIndicator.

Ejemplo Cookbook de Abril 03, 2019

Null No Tiene Datos

Nuevamente, con la primera compilación, el valor, snapshot.hasData, es falso y da como resultado un loading spinner que se muestra en el centro de la pantalla. A continuación, puede ver que esta expresión booleana, snapshot.hasData, proviene de un “captador” en el objeto AsyncSnapshot que verifica si la propiedad, los datos, son nulos o no.

hasData en la Clase AsyncSnapshot Abril 03, 2019

Data De Tipo T

Con el uso de Genéricos, el objeto AsyncSnapshot definido en la función initState() de FutureBuilder tendrá la propiedad denominada, datos, definida como una clase de tipo, Publicación. Nuevamente, si hubiera un valor initialData pasado a FutureBuilder, la propiedad, los datos, se habrían establecido ese valor. De lo contrario, como en este caso, la propiedad, los datos, se establece en nulo con el objeto Futuro que aún no puede proporcionar un valor resultante.

Tipo de dato Genérico, T, de la propiedad data en la Clase AsyncSnapshot

Cualquier Error?

Una buena práctica es incluir también en el método anónimo asignado al parámetro, builder, una llamada al getter, hasError. También se encuentra en el objeto AsyncSnapshot, y también implica verificar si una propiedad es nula o no. En este caso, es la propiedad llamada, error.

hasError en la Clase AsyncSnapshot Abril 03, 2019

Entonces, ¿qué pasa después?

En este ejemplo de Cookbook con su primer build, el objeto Future está incompleto. Entonces, ¿qué pasa después? Lo que suceda a continuación depende del objeto Future especificado. Después de todo, todavía no se ha completado la operación asíncrona en este punto. ¿Recuerda el método llamado _subscribe(), llamado en la función initState() de FutureBuilder? Es allí donde ocurre la verdadera magia.

Donde ocurre la magia

Una captura de pantalla del método _subscribe () se muestra a continuación. Es en este método que la función de entonces se define para el objeto Future originalmente especificado con FutureBuilder. Y así, cuando el objeto Future complete su operación asíncrona, se llamará el método anónimo definido y pasado como la función de devolución de llamada en la función then. En este caso, se llama cuando los datos se obtienen de un sitio web.

Tenga en cuenta que el parámetro de esta función de devolución de llamada en particular es un tipo genérico que ha visto antes: de tipo, T. Por lo tanto, un parámetro de un tipo de clase, post, se pasa a la función de devolución de llamada.

_subscribe() en _FutureBuilderState<T> Abril 03, 2019

Tenga en cuenta que además, un parámetro con nombre adicional que define una función de devolución de llamada ‘onError’ también se pasa a la función then. Se llama si el objeto Future completa su operación asíncrona con un error. Si hay un error, se proporciona un objeto de clase como parámetro. En la mayoría de los casos, será del tipo de clase, Excepción.

El Future se Completa

Entonces, ¿qué pasa después? Bueno, en este ejemplo, hasta que se obtengan los datos del sitio web, hay un spinner que hace su trabajo en el centro de la pantalla. Sin embargo, como puede ver a continuación, cuando la operación asincrónica se completa y los datos se obtienen del sitio web, ya sea en forma exitosa o por error, se crea un nuevo objeto AsyncSnapshot inmutable con el tipo enumerado, ConnectionState.done, que se le pasa.

_subscribe() en _FutureBuilderState<T> Abril 03, 2019

Por supuesto, debido a la llamada a la función setState(), la función build() de FutureBuilder se activará nuevamente pero ahora pasará el objeto AsyncSnapshot, _snapshot, con su “estado de conexión” establecido en ConnectionState.done. Pasó de nuevo a ese método anónimo asignado a la propiedad “constructor”. Vea abajo. Con cada llamada a la función setState(), esto sucederá.

Función build() del FutureBuilder Abril 03, 2019

El estado de conexión del flujo Future

Entonces, revisemos el flujo de control una vez más. Al principio de la compilación, sabemos que el objeto Futuro aún no está listo (vemos al spinner en el centro de la pantalla por un tiempo). Por lo tanto, si observamos la instantánea en el método del “builder” en ese punto, encontraríamos que el valor de snapshot.connectionState está establecido en ConnectionState.waiting.

Para demostrar esto, hay puntos de interrupción establecidos a lo largo de una sentencia Switch Case ahora insertada en la función de build() a continuación. Con la primera compilación, se detiene donde el estado de conexión se establece actualmente en ConnectionState.waiting.

Ahora, ¿dónde cambió el estado de conexión de ConnectionState.none a ConnectionState.waiting? Bueno, te lo mostraré. Recuerde, cuando se utiliza StatefulWidgets, el objeto State asociado llama una llamada “única” de una función initState(). Con los widgets de FutureBuilder, es allí donde primero se crea un objeto AsyncSnapshot con el parámetro, ConnectionState.none, y luego se llama a la función _subscribe().

initState() en FutureBuilder Abril 03, 2019

Fijemos Estamos Esperando

Es en la función, _subscribe(), donde vemos el nuevo estado de conexión. Por su propia naturaleza, las operaciones asíncronas llevan tiempo. En la mayoría de los casos, el objeto Future aún está incompleto cuando se llama a la función _subscribe(). Dándole tiempo a esa función para definir la función then del objeto Futuro. Tenga en cuenta que después de definir la función then, se crea un nuevo objeto AsyncSnapshot inmutable que reemplaza al anterior y ahora proporciona un conjunto de estados de conexión en “espera”.

_subscribe() en _FutureBuilderState<T> Abril 03, 2019

Y así, para este ejemplo del Cookbook, así es como se mantendrán las cosas por un segundo: el widget CircularProgressIndicator que se muestra en el centro de la pantalla y un objeto AsyncSnapshot con un estado de conexión de “espera”. Por supuesto, he cambiado las cosas. un poco con la sentencia Switch Case ahora en la función build(). En lugar de una flecha giratoria que se muestra en el centro de la pantalla, simplemente tenemos el texto, ‘ConnectionState.waiting’, en el centro de la pantalla. En cualquier caso, cuando la operación asincrónica se complete (los datos obtenidos de un sitio web), las cosas cambiarán.

Cuando es Done, es Dato

En las capturas de pantalla a continuación, vemos la progresión de la operación asíncrona. Mientras esperábamos, vemos que se llamó a un widget de texto para mostrar la línea, ‘ConnectionState.waiting’, en el centro de la pantalla. Sin embargo, en un instante, el valor de snapshot.connectionState se establece: ConnectionState.done. Un breakpoint fue colocado allí en el instante.

Tenga en cuenta que el widget de texto no está acompañado por un comando return. En su lugar, la ejecución continuará encontrando la instrucción if con la expresión booleana, snapshot.hasData, se establece en verdadero. Por lo tanto, dado que la propiedad, data, es de tipo de clase, Post, su propiedad, title, proporcionará una cadena JSON a otro widget de Texto que se devuelve desde el método del generador y luego se muestra en la pantalla. Simple como eso.

Entonces, ¿qué pasó allí?

Cuando se complete la operación asíncrona, se ejecutará la función callback en la función then del objeto Futuro. Así que en la función then para ese objeto Future específico, cuando finalmente se completó la recuperación de datos, el parámetro, data, se pasó al constructor “withData” para crear otro nuevo objeto AsyncSnapshot inmutable.

_subscribe() en _FutureBuilderState<T> April 03, 2019

Al ser un tipo de datos genérico, sabemos que este parámetro, data, es del tipo de clase, Post. Además, el valor, ConnectionState.done, también se pasa al constructor. Todo esto dentro de una función setState(), y por lo tanto, la función build() será llamada nuevamente. Esta vez, la propiedad, snapshot.hasData, será verdadera, lo que permitirá que la aplicación finalmente proceda con el texto JSON que se muestra en la pantalla.

¿Qué sucede en error?

Entonces, eso fue genial cuando todo se junta y funciona. Veamos qué sucede cuando introducimos una URI con formato incorrecto en el ejemplo del Cookbook. Veremos cómo reacciona el widget FuturBuilder y el objeto AsyncSnapshot que lo acompaña cuando falla la operación asíncrona.

Ejemplo Cookbook abril 03, 2019

En la captura de pantalla a continuación, vemos que el error HTTP 404 No encontrado está asignado a response.statusCode y, en lugar de un objeto futuro ‘completo’ del tipo de clase Post, un objeto de excepción con el mensaje, ‘No se pudo cargar la publicación ‘, es en cambio instanciado. La operación asíncrona se ha completado con error.

Y así, si y cuando usted mismo desarrolle la ‘operación asíncrona’ en sus aplicaciones, recuerde lanzar excepciones cuando sea apropiado saber que el widget FutureBuilder tiene los medios ‘para detectar’ tales errores. Ahora sabe que hay una función then con su parámetro ‘onError’ asignado a un método.

Entonces, en el ejemplo de Cookbook en este caso, cuando la ejecución sale de la función fetchPost() por error, nos encontramos en la función then de objeto futuro especificada. El objeto de excepción arrojado en la captura de pantalla anterior ahora se suministra a la función de devolución de llamada ‘onError’.

_subscribe en FutureBuilder Abr 3, 2019

De nuevo, con la ejecución de una función setState(), se pasa un nuevo objeto AsyncSnaphot (de tipo Post) a la función build() de FutureBuilder. Esta vez, sin embargo, se suministra con un Objeto de tipo, Excepción.

Función build() de FutureBuilder Abr 3, 2019

De nuevo, se llama a la función builder() y, en consecuencia, se ejecuta el método anónimo. No hay “datos disponibles” en el objeto AsyncSnapshot, instantánea, esta vez. En su lugar, la expresión snapshot.hasError se establece en verdadero y se muestra el mensaje de error real, “Excepción: No se pudo cargar la publicación”.

Tenga en cuenta que la propiedad, error, en el objeto AsyncSnapshot, instantánea, es de tipo Objeto y no específicamente de tipo Excepción. Ahora porque es eso?

Propiedad error, en AsyncSnapshot class

La propiedad error, en el objeto AsyncSnapshot snapshot, es de tipo Objeto, por lo que puede asignar cualquier tipo de clase que no sea Exception. La clase Object, es la clase base de todos los objetos en Dart, después de todo, por lo tanto, el desarrollador tiene la libertad de asignar cualquier objeto que considere adecuado siempre y cuando falle la operación asíncrona. Podría ser un objeto que amplíe la clase Exception, por ejemplo, o una clase que ellos mismos hayan inventado. Te da opciones. Opciones de amor.

TLDR

Eso debería bastar. ¿No te parece? El resto de este artículo es sólo salsa.

¿Qué hay con este callbackIdentity?

En la función _subscribe(), observará dos declaraciones if que implican la siguiente expresión, _activeCallbackIdentity == callbackIdentity. Justo antes de las dos funciones setState(). Es un pequeño truco que involucra el uso de la clase base para todos los objetos Dart llamados … bien … Objeto. Este objeto se define cerca del inicio de la función y se asigna a una variable final: callbackIndentity = Object();

_subscribe() en _FutureBuilderState<T> Abril 03, 2019

Verás, con cosas asíncronas, pueden pasar muchas cosas entre el momento en que se llama a la función _subscribe() y el momento en que finalmente finaliza la operación asíncrona. En otras palabras, en el momento en que se complete la operación asíncrona, estos pueden ser circunstanciados donde estas sentencias if podrian en efecto ser establecidas en false por una razón u otra. (es decir, la expresión, _activeCallbackIdentity == callbackIdentity, ya no es verdadera).

_subscribe() en _FutureBuilderState<T> Abril 03, 2019

Por ejemplo, este pequeño truco impide llamar a la función setState() en un objeto de estado que ya se ha eliminado (es decir, el widget de FutureBuilder ha finalizado). En otro caso, es para evitar que se llame a la función setState() justo después de que se haya creado una instancia del objeto Future especificado porque el Widget de FutureBuilder fue “reconstruido” por una razón u otra que hace que la propiedad, _activeCallbackIdentity, se establezca en nulo.

Durante el ciclo de vida de la aplicación móvil, por ejemplo, el StatefulWidget que es el FutureBuilder puede ser reconstruido. Esto hará que se llame a la función didUpdateWidget() del objeto State tomando una copia del widget FutureBuilder “antiguo” como parámetro.

Clase FutureBuilder Abril 03, 2019

En esta función, mirando la primera sentencia if, si el objeto Future especificado también ha cambiado de alguna manera, se llama a la función _subscribe(). Sin embargo, si la función _subscribe() se llamó antes porque _activeCallbackIdentity! = Null, la función, _unsubscribe(), también se llama para establecer la propiedad, _activeCallbackIdentity, en nulo. Un nuevo objeto AsyncSnapshot se crea una instancia con los mismos datos pero se le asigna el estado de conexión, ‘ninguno’ solo para ser reemplazado poco después por otro nuevo objeto AsyncSnapshot nuevo con el estado de conexión de ‘espera’ en la llamada subsiguiente a la función _subscribe( ).

El Future ha sido Dispuesto

De nuevo, la expresión, _activeCallbackIdentity == callbackIdentity, también se usa porque puede darse el caso de que se complete la operación asíncrona del objeto Future, solo para encontrar que el widget FutureBuilder ha finalizado y se ha eliminado su objeto State. En otras palabras, la expresión, _activeCallbackIdentity == callbackIdentity, es falsa ya que la propiedad, _activeCallbackIdentity, se establece en nula.

Un Future Opcional?

¿Notó que, en la función _subscribe(), el código está incluido en una sola sentencia if? A primera vista, esto implica que FutureBuilder no necesita proporcionar un objeto Future a su constructor. Sin embargo, lo que sí significa es que el objeto Future proporcionado puede ser nulo en un momento u otro durante el ciclo de vida de FutureBuilder.

Un Builder es una necesidad

Al observar la clase FutureBuilder, vemos que, de hecho, es la propiedad builder, la que debe pasarse al constructor. Vemos la anotación @required, y una sentencia assert que enfatiza ese hecho. Sin embargo, debido a la naturaleza misma de los parámetros nombrados, los parámetros del constructor, future e initialData, son opcionales. Por lo tanto, la propiedad future, puede ser nula.

No Future No Datos No Error

Entonces, ¿qué sucede si no proporcionó un objeto Future a ese ejemplo de Cookbook? Bueno, se espera que, después de leer este artículo, puedas deducir que lo que sucedería es que aparecerá una flecha en el centro de la pantalla.

Por qué No Future?

De nuevo, el parámetro future no pretende ser opcional. Es para permitir que el objeto Future pasado sea nulo en algún momento en el tiempo, generalmente al principio. Por ejemplo, si la aplicación de ejemplo Cookbook tenía el FutureBuiler en un objeto State y no en un StatelessWidget, esto implica que ese FutureBuilder podría ser llamado una y otra vez (el widget reconstruido una y otra vez), y así permitir que un objeto Future suministrado sea nulo por una razón u otra durante una de esas llamadas.

Si y cuando es nulo, ya sabes lo que pasará. El generador se activará, el Estado de conexión será, ConnectionState.waiting y tanto snapshot.hasData como snapshot.hasError serán falsos.

Es unFuture con un Then

Entonces, ¿qué es un FutureButilder en pocas palabras? Esencialmente, un FutureBuilder es un objeto Futuro asignado a un método then mientras se encuentra dentro de un StatefulWidget. La función anónima definida en el método then dispara las funciones setState() cuando la operación asíncrona finalmente se completa con éxito o no. Esto, por supuesto, activa la función de build() del objeto de estado envolvente, lo que permite que la función anónima suministrada por el generador de parámetros nombrado por FutureBuilder, se ejecute nuevamente, devolviendo el widget apropiado basado en el resultado de la operación asíncrona.

Eso es todo.

Comunidad Flutter

Artículos e Historias de la Comunidad de Flutter

CarlosMillan

Written by

Dart / Flutter developer and space lover.

Comunidad Flutter

Artículos e Historias de la Comunidad de Flutter

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade