¿Qué es un futuro y cómo lo uso?
Obtuve el siguiente error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
Podría ser otro tipo en lugar de int
, pero básicamente el patrón es:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
Entonces:
- ¿Qué es exactamente un
Future
? - ¿Cómo obtengo el valor real que quiero obtener?
- ¿Qué widget uso para mostrar mi valor cuando todo lo que tengo es un
Future<T>
?
En caso de que esté familiarizado con Task<T>
o Promise<T>
y el patrón async
/ await
, puede pasar directamente a la sección "Cómo usar un Future con los widgets en Flutter".
¿Qué es un futuro y cómo lo uso?
Bueno, la documentación dice:
Un objeto que representa un cálculo retrasado.
Eso es correcto. También es un poco abstracto y seco. Normalmente, una función devuelve un resultado. Secuencialmente. La función se llama, se ejecuta y devuelve su resultado. Hasta entonces, la persona que llama espera. Algunas funciones, especialmente cuando acceden a recursos como hardware o red, tardan un poco en hacerlo. Imagine que se carga una imagen de avatar desde un servidor web, que se cargan los datos de un usuario desde una base de datos o simplemente que se cargan los textos de la aplicación en varios idiomas desde la memoria del dispositivo. Puede que eso sea lento.
La mayoría de las aplicaciones tienen de forma predeterminada un único flujo de control. Cuando este flujo se bloquea, por ejemplo al esperar un cálculo o un acceso a recursos que lleva tiempo, la aplicación simplemente se congela. Es posible que recuerdes esto como estándar si tienes la edad suficiente, pero en el mundo actual eso se consideraría un error. Incluso si algo lleva tiempo, obtenemos una pequeña animación. Una ruleta, un reloj de arena, tal vez una barra de progreso. Pero, ¿cómo puede una aplicación ejecutarse y mostrar una animación y aún así esperar el resultado? La respuesta es: operaciones asincrónicas. Operaciones que aún se ejecutan mientras su código espera algo. Ahora bien, ¿cómo sabe el compilador si realmente debería detener todo y esperar un resultado o continuar con todo el trabajo en segundo plano y esperar solo en este caso? Bueno, no puede resolverlo por sí solo. Tenemos que contarlo .
Esto se logra mediante un patrón conocido comoasíncronoyesperar. No es específico dealeteoodardo, existe con el mismo nombre en muchos otros idiomas. Puede encontrar la documentación de Dart aquí .
Dado que un método que tarda algún tiempo no puede regresar inmediatamente, devolverá la promesa de entregar un valor cuando esté terminado.
Eso se llama Future
. Entonces, la promesa de cargar un número de la base de datos devolvería un Future<int>
tiempo. La promesa de devolver una lista de películas de una búsqueda en Internet podría devolver un archivo Future<List<Movie>>
. A Future<T>
es algo que en el futuro te dará un T
.
Intentemos una explicación diferente:
Un futuro representa el resultado de una operación asincrónica y puede tener dos estados: incompleto o completado.
Lo más probable es que, como no estás haciendo esto sólo por diversión, en realidad necesites los resultados Future<T>
para progresar en tu aplicación. Debe mostrar el número de la base de datos o la lista de películas encontradas. Entonces quieres esperar hasta que el resultado esté ahí. Aquí es donde await
entra en juego:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
Pero espera, ¿no hemos cerrado el círculo? ¿No estamos esperando de nuevo el resultado? Sí, efectivamente lo somos. Los programas serían completamente caóticos si no tuvieran cierta apariencia de flujo secuencial. Pero el punto es que usando la palabra clave await
le hemos dicho al compilador que en este punto, mientras queremos esperar el resultado, no queremos que nuestra aplicación simplemente se congele. Queremos que continúen todas las demás operaciones en ejecución, como por ejemplo las animaciones.
Sin embargo, solo puede utilizar la await
palabra clave en funciones que estén marcadas como async
y devuelvan un archivo Future<T>
. Porque cuando haces await
algo, la función que está esperando ya no puede devolver su resultado inmediatamente. Sólo puedes devolver lo que tienes, si tienes que esperar, tienes que devolver una promesa de entregarlo más tarde.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Nuestra función getPizza tiene que esperar la pizza, por lo que en lugar de regresar Pizza
inmediatamente, tiene que devolver la promesa de que habrá una pizza allí en el futuro . Ahora puedes, a su vez, activar await
la función getPizza en alguna parte.
¿Cómo usar un Future con los widgets en Flutter?
Todos los widgets en flutter esperan valores reales. No es una promesa de un valor que vendrá más adelante. Cuando un botón necesita un texto, no puede utilizar la promesa de que el texto aparecerá más tarde. Necesita mostrar el botón ahora , por lo que necesita el texto ahora .
Pero a veces lo único que tienes es un Future<T>
. Ahí es donde FutureBuilder
entra en juego. Puedes usarlo cuando tengas un futuro, para mostrar una cosa mientras lo esperas (por ejemplo, un indicador de progreso) y otra cosa cuando esté terminado (por ejemplo, el resultado).
Echemos un vistazo a nuestro ejemplo de pizza. Quiere pedir pizza, quiere un indicador de progreso mientras la espera, quiere ver el resultado una vez que se entrega y tal vez mostrar un mensaje de error cuando haya un error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
@override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
Así es como usas a FutureBuilder
para mostrar el resultado de tu futuro una vez que lo tienes.
Aquí hay una lista de analogías con Dart Future
en otros idiomas:
- JS:
Promise
- Java:
Future
- Pitón:
Future
- C#:
Task
Al igual que en otros lenguajes, Future es un tipo especial de objeto que permite utilizar la sintaxis async/await sugar, escribir código asincrónico de forma síncrona/lineal. Devuelve Future desde un método asíncrono en lugar de aceptar una devolución de llamada como parámetro y evitar el infierno de las devoluciones de llamada: tanto Futures como las devoluciones de llamada resuelven los mismos problemas (activando algo de código en un momento posterior) pero de una manera diferente.
Future<T>
devolver el valor potencial que se obtendrá mediante async
el trabajo
P.ej:
Future<int> getValue() async {
return Future.value(5);
}
El código anterior devuelve Future.value(5)
cuál es de int
tipo, pero mientras recibimos el valor del método no podemos usar el tipo, es Future<int>
decir
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
Para resolver lo anterior, getValue() debe recibirse en int
el tipo
int value = await getValue(); // right way as it returning the potential value.