Saltar al contenido principal

Hot Reload

Acelera el desarrollo utilizando la función Hot Reload de Flutter.

La función de Hot Reload de Flutter te ayuda a experimentar, construir UIs, agregar funciones y solucionar errores de manera rápida y sencilla. Hot reload funciona inyectando archivos de código fuente actualizados en el Dart runtime. Después de que el Dart runtime actualiza las clases con las nuevas versiones de campos y funciones, el framework de Flutter reconstruye automáticamente el árbol de Widgets, lo que te permite ver rápidamente los efectos de tus cambios.

GIF de Hot reload
Una demostración de Hot Reload en DartPad

Cómo realizar un hot reload

#

Si estás utilizando un asistente de código AI como Google Antigravity, puedes usar el modo Agente para hacer hot reload automáticamente en tu aplicación en ejecución tan pronto como le pidas al agente que aplique cambios. Para más detalles, consulta Agentic Hot Reload.

Para realizar un hot reload de una app Flutter manualmente:

  1. Ejecuta la app desde un editor de Flutter compatible o desde una ventana de terminal. Tanto un dispositivo físico como uno virtual pueden ser el objetivo. Solo las apps de Flutter en modo de depuración (debug) se pueden recargar con Hot Reload o reiniciar con Hot Restart.

  2. Modifica uno de los archivos Dart de tu proyecto. La mayoría de los tipos de cambios de código se pueden recargar con Hot Reload; para obtener una lista de cambios que requieren un Hot Restart, consulta Casos especiales.

  3. Si estás trabajando en un IDE/editor que admite las herramientas IDE de Flutter y el hot reload al guardar está habilitado, selecciona Save All (cmd-s/ctrl-s), o haz clic en el botón de hot reload en la barra de herramientas.

    Si estás ejecutando la app en la línea de comandos usando flutter run, ingresa r en la ventana de la terminal.

Después de una operación de hot reload exitosa, verás un mensaje en la consola similar a:

Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.

La app se actualiza para reflejar tu cambio, y se conserva el estado actual de la app. Tu app continúa ejecutándose desde donde estaba antes de ejecutar el comando de hot reload. El código se actualiza y la ejecución continúa.

UI de Android Studio
Controles para run, run debug, hot reload y hot restart en Android Studio

Un cambio de código tiene un efecto visible solo si el código Dart modificado se vuelve a ejecutar después del cambio. Específicamente, un hot reload hace que todos los Widgets existentes se vuelvan a construir. Solo el código involucrado en la reconstrucción de los Widgets se vuelve a ejecutar automáticamente. Las funciones main() e initState(), por ejemplo, no se vuelven a ejecutar.

Casos especiales

#

Las siguientes secciones describen escenarios específicos que involucran hot reload. En algunos casos, pequeños cambios en el código Dart te permiten continuar usando hot reload para tu app. En otros casos, se necesita un hot restart o un full restart.

Se finaliza una aplicación

#

Hot reload puede fallar cuando se finaliza la aplicación. Por ejemplo, si la app estuvo en segundo plano durante mucho tiempo.

Errores de compilación

#

Cuando un cambio de código introduce un error de compilación, hot reload genera un mensaje de error similar a:

Hot reload was rejected:
'/path/to/project/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here
  Widget build(BuildContext context) {
                                     ^
'/path/to/project/lib/main.dart': error: line 33 pos 5: unbalanced ')'
    );
    ^

En esta situación, simplemente corrige los errores en las líneas especificadas de código Dart para seguir usando hot reload.

Builder de CupertinoTabView

#

Hot reload no aplicará los cambios realizados a un builder de un CupertinoTabView. Para obtener más información, consulta Issue 43574.

Tipos enumerados

#

Hot reload no funciona cuando los tipos enumerados se cambian a clases regulares o las clases regulares se cambian a tipos enumerados.

Por ejemplo:

Antes del cambio:

dart
enum Color { red, green, blue }

Después del cambio:

dart
class Color {
  Color(this.i, this.j);
  final int i;
  final int j;
}

Tipos genéricos

#

Hot reload no funcionará cuando se modifiquen las declaraciones de tipos genéricos. Por ejemplo, lo siguiente no funcionará:

Antes del cambio:

dart
class A<T> {
  T? i;
}

Después del cambio:

dart
class A<T, V> {
  T? i;
  V? v;
}

Código nativo

#

Si has cambiado código nativo (como Kotlin, Java, Swift, o Objective-C), debes realizar un full restart (detener y reiniciar la app) para ver que los cambios surtan efecto.

El estado anterior se combina con el nuevo código

#

El hot reload con estado de Flutter conserva el State de tu app. Este enfoque te permite ver el efecto solo del cambio más reciente, sin descartar el State actual. Por ejemplo, si tu app requiere que un usuario inicie sesión, puedes modificar y hacer hot reload a una página varios niveles abajo en la jerarquía de navegación, sin volver a ingresar tus credenciales de inicio de sesión. El State se mantiene, lo cual suele ser el comportamiento deseado.

Si los cambios de código afectan el State de tu app (o sus dependencias), los datos con los que tiene que trabajar tu app podrían no ser completamente consistentes con los datos que tendría si se ejecutara desde cero. El resultado podría ser un comportamiento diferente después de un hot reload en comparación con un hot restart.

Se incluye el cambio de código reciente pero se excluye el estado de la app

#

En Dart, los campos estáticos se inicializan de forma perezosa (lazily). Esto significa que la primera vez que ejecutas una app Flutter y se lee un campo estático, se establece en cualquier valor al que se haya evaluado su inicializador. Las variables globales y los campos estáticos se tratan como State, y por lo tanto no se vuelven a inicializar durante el hot reload.

Si cambias los inicializadores de variables globales y campos estáticos, es necesario un hot restart o reiniciar el estado donde se mantienen los inicializadores para ver los cambios. Por ejemplo, considera el siguiente código:

dart
final sampleTable = [
  Table(
    children: const [
      TableRow(children: [Text('T1')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T2')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T3')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T4')]),
    ],
  ),
];

Después de ejecutar la app, realizas el siguiente cambio:

dart
final sampleTable = [
  Table(
    children: const [
      TableRow(children: [Text('T1')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T2')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T3')]),
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T10')], // modified
      ),
    ],
  ),
];

Das hot reload, pero el cambio no se refleja.

Por el contrario, en el siguiente ejemplo:

dart
const foo = 1;
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

Ejecutar la app por primera vez imprime 1 y 1. Luego, realizas el siguiente cambio:

dart
const foo = 2; // modified
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}

Mientras que los cambios en los valores de los campos const siempre se recargan con hot reload, el inicializador del campo estático no se vuelve a ejecutar. Conceptualmente, los campos const se tratan como alias en lugar de State.

La VM de Dart detecta los cambios en los inicializadores y marca cuándo un conjunto de cambios necesita un hot restart para surtir efecto. El mecanismo de marcado se activa para la mayor parte del trabajo de inicialización en el ejemplo anterior, pero no para casos como el siguiente:

dart
final bar = foo;

Para actualizar foo y ver el cambio después del hot reload, considera redefinir el campo como const o usar un getter para retornar el valor, en lugar de usar final. Por ejemplo, cualquiera de las siguientes soluciones funciona:

dart
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
  print(foo);
  print(bar);
}
dart
const foo = 1;
int get bar => foo; // ...or provide a getter.
void onClick() {
  print(foo);
  print(bar);
}

Para obtener más información, lee sobre las diferencias entre las palabras clave const y final en Dart.

Se excluye el cambio de UI reciente

#

Incluso cuando una operación de hot reload parece exitosa y no genera excepciones, es posible que algunos cambios de código no sean visibles en la UI actualizada. Este comportamiento es común después de cambios en los métodos main() o initState() de la aplicación.

Como regla general, si el código modificado está aguas abajo del método build() del Widget raíz, entonces hot reload se comporta como se espera. Sin embargo, si el código modificado no se volverá a ejecutar como resultado de reconstruir el árbol de Widgets, entonces no verás sus efectos después del hot reload.

Por ejemplo, considera el siguiente código:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(onTap: () => print('tapped'));
  }
}

Después de ejecutar esta app, cambia el código de la siguiente manera:

dart
import 'package:flutter/widgets.dart';

void main() {
  runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr)));
}

Con un hot restart, el programa comienza desde el principio, ejecuta la nueva versión de main(), y construye un árbol de Widgets que muestra el texto Hello.

Sin embargo, si haces hot reload en la app después de este cambio, main() e initState() no se vuelven a ejecutar, y el árbol de Widgets se reconstruye con la instancia sin cambios de MyApp como Widget raíz. Esto da como resultado ningún cambio visible después del hot reload.

Cómo funciona

#

Cuando se invoca hot reload, la máquina host mira el código editado desde la última compilación. Se recompilan las siguientes librerías:

  • Cualquier librería con código cambiado
  • La librería principal de la aplicación
  • Las librerías de la librería principal que conducen a las librerías afectadas

El código fuente de esas librerías se compila en archivos kernel y se envía a la VM de Dart del dispositivo móvil.

La VM de Dart vuelve a cargar todas las librerías desde el nuevo archivo kernel. Hasta ahora no se ha vuelto a ejecutar ningún código.

El mecanismo de hot reload luego hace que el framework de Flutter active una reconstrucción/rediseño/repintado de todos los Widgets existentes y RenderObjects.