Saltar al contenido principal

Errores comunes de Flutter

Cómo reconocer y resolver errores comunes del framework de Flutter.

Introducción

#

Esta página explica varios errores del framework de Flutter frecuentemente encontrados (incluidos errores de layout) y ofrece sugerencias sobre cómo resolverlos. Este es un documento vivo al que se añadirán más errores en futuras revisiones, y tus contribuciones son bienvenidas. Siéntete libre de abrir un issue o enviar un pull request para hacer que esta página sea más útil para ti y para la comunidad de Flutter.

Una pantalla roja o gris sólida al ejecutar tu app

#

Normalmente llamada "pantalla roja (o gris) de la muerte", a veces es así como Flutter te permite saber que hay un error.

La pantalla roja puede aparecer cuando la app se ejecuta en modo debug o profile. La pantalla gris puede aparecer cuando la app se ejecuta en modo release.

Generalmente, estos errores ocurren cuando hay una excepción no capturada (y podrías necesitar otro bloque try-catch), o cuando hay algún error de renderizado, como un error de desbordamiento (overflow).

Los siguientes artículos proporcionan información útil sobre la depuración de este tipo de errores:

'Un RenderFlex se desbordó…'

#

El desbordamiento de RenderFlex es uno de los errores del framework de Flutter frecuentemente encontrados, y probablemente ya te hayas topado con él.

What does the error look like?

Cuando sucede, aparecen franjas amarillas y negras, indicando el área de desbordamiento en la UI de la app. Además, se muestra un mensaje de error en la consola de depuración:

The following assertion was thrown during layout:
A RenderFlex overflowed by 1146 pixels on the right.

The relevant error-causing widget was

    Row      lib/errors/renderflex_overflow_column.dart:23

The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the rendering
with a yellow and black striped pattern. This is usually caused by the contents
being too big for the RenderFlex.
(Additional lines of this message omitted)

How might you run into this error?

El error ocurre a menudo cuando una Column o un Row tiene un widget hijo que no está restringido en su tamaño. Por ejemplo, el fragmento de código a continuación demuestra un escenario común:

dart
Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.message),
      Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Title', style: Theme.of(context).textTheme.headlineMedium),
          const Text(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed '
            'do eiusmod tempor incididunt ut labore et dolore magna '
            'aliqua. Ut enim ad minim veniam, quis nostrud '
            'exercitation ullamco laboris nisi ut aliquip ex ea '
            'commodo consequat.',
          ),
        ],
      ),
    ],
  );
}

En el ejemplo anterior, la Column intenta ser más ancha que el espacio que el Row (su padre) puede asignarle, causando un error de desbordamiento. ¿Por qué intenta hacer eso la Column? Para entender este comportamiento de layout, necesitas saber cómo realiza el layout el framework de Flutter:

"Para realizar el layout, Flutter recorre el árbol de renderizado en un recorrido primero en profundidad y pasa hacia abajo las restricciones de tamaño de padre a hijo… Los hijos responden pasando hacia arriba un tamaño a su objeto padre dentro de las restricciones que el padre estableció." – Visión general de la arquitectura de Flutter

En este caso, el widget Row no restringe el tamaño de sus hijos, ni tampoco el widget Column. Al carecer de restricciones de su widget padre, el segundo widget Text intenta ser tan ancho como todos los caracteres que necesita mostrar. El ancho autodeterminado del widget Text es entonces adoptado por la Column, lo que choca con la cantidad máxima de espacio horizontal que su padre, el widget Row, puede proporcionar.

How to fix it?

Bueno, necesitas asegurarte de que la Column no intentará ser más ancha de lo que puede ser. Para lograr esto, necesitas restringir su ancho. Una forma de hacerlo es envolver la Column en un widget Expanded:

dart
return const Row(
  children: [
    Icon(Icons.message),
    Expanded(
      child: Column(
        // code omitted
      ),
    ),
  ],
);

Otra forma es envolver la Column en un widget Flexible y especificar un factor flex. De hecho, el widget Expanded es equivalente al widget Flexible con un factor flex de 1.0, como muestra su código fuente. Para comprender mejor cómo usar el widget Flex en layouts de Flutter, consulta este video de 90 segundos de Widget of the Week sobre el widget Flexible.

Further information:

Los recursos enlazados a continuación proporcionan más información sobre este error.

'RenderBox no fue dispuesto (laid out)'

#

Si bien este error es bastante común, a menudo es un efecto secundario de un error primario que ocurrió anteriormente en el pipeline de renderizado.

What does the error look like?

El mensaje mostrado por el error se ve así:

RenderBox was not laid out:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE

How might you run into this error?

Por lo general, el problema está relacionado con la violación de las restricciones de caja (box constraints), y debe resolverse proporcionando más información a Flutter sobre cómo te gustaría restringir los widgets en cuestión. Puedes obtener más información sobre cómo funcionan las restricciones en Flutter en la página Entendiendo las restricciones.

El error RenderBox no fue dispuesto (laid out) a menudo es causado por uno de otros dos errores:

  • 'A la ventana gráfica vertical (viewport) se le dio una altura no delimitada (unbounded)'
  • 'Un InputDecorator...no puede tener un ancho no delimitado (unbounded)'

'A la ventana gráfica vertical (viewport) se le dio una altura no delimitada (unbounded)'

#

Este es otro error de layout común con el que podrías toparte al crear una UI en tu app de Flutter.

What does the error look like?

El mensaje mostrado por el error se ve así:

The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.

Viewports expand in the scrolling direction to fill their container.
In this case, a vertical viewport was given an unlimited amount of
vertical space in which to expand. This situation typically happens when a
scrollable widget is nested inside another scrollable widget.
(Additional lines of this message omitted)

How might you run into this error?

El error se causa a menudo cuando un ListView (u otros tipos de widgets desplazables como GridView) se coloca dentro de una Column. Un ListView toma todo el espacio vertical disponible para él, a menos que esté restringido por su widget padre. Sin embargo, una Column no impone ninguna restricción en la altura de sus hijos por defecto. La combinación de los dos comportamientos conduce al fallo al determinar el tamaño del ListView.

dart
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        ListView(
          children: const <Widget>[
            ListTile(leading: Icon(Icons.map), title: Text('Map')),
            ListTile(leading: Icon(Icons.subway), title: Text('Subway')),
          ],
        ),
      ],
    ),
  );
}

How to fix it?

Para solucionar este error, especifica qué tan alto debe ser el ListView. Para hacerlos tan alto como el espacio restante en la Column, envuélvelo usando un widget Expanded (como se muestra en el siguiente ejemplo). De lo contrario, especifica una altura absoluta usando un widget SizedBox o una altura relativa usando un widget Flexible.

dart
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        Expanded(
          child: ListView(
            children: const <Widget>[
              ListTile(leading: Icon(Icons.map), title: Text('Map')),
              ListTile(leading: Icon(Icons.subway), title: Text('Subway')),
            ],
          ),
        ),
      ],
    ),
  );
}

Further information:

Los recursos enlazados a continuación proporcionan más información sobre este error.

'Un InputDecorator...no puede tener un ancho no delimitado (unbounded)'

#

El mensaje de error sugiere que también está relacionado con las restricciones de caja (box constraints), las cuales son importantes de entender para evitar muchos de los errores más comunes del framework de Flutter.

What does the error look like?

El mensaje mostrado por el error se ve así:

The following assertion was thrown during performLayout():
An InputDecorator, which is typically created by a TextField, cannot have an
unbounded width.
This happens when the parent widget does not provide a finite width constraint.
For example, if the InputDecorator is contained by a `Row`, then its width must
be constrained. An `Expanded` widget or a SizedBox can be used to constrain the
width of the InputDecorator or the TextField that contains it.
(Additional lines of this message omitted)

How might you run into the error?

Este error ocurre, por ejemplo, cuando un Row contiene un TextFormField o un TextField pero este último no tiene restricción de ancho.

dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
      body: const Row(children: [TextField()]),
    ),
  );
}

How to fix it?

Como sugiere el mensaje de error, soluciona este error restringiendo el campo de texto usando un widget Expanded o SizedBox. El siguiente ejemplo demuestra el uso de un widget Expanded:

dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
      body: Row(children: [Expanded(child: TextFormField())]),
    ),
  );
}

'Uso incorrecto del widget ParentData'

#

Este error se debe a la falta de un widget padre esperado.

What does the error look like?

El mensaje mostrado por el error se ve así:

The following assertion was thrown while looking for parent data:
Incorrect use of ParentDataWidget.
(Some lines of this message omitted)
Usually, this indicates that at least one of the offending ParentDataWidgets
listed above is not placed directly inside a compatible ancestor widget.

How might you run into the error?

Si bien los widgets de Flutter son generalmente flexibles en cómo se pueden componer juntos en una UI, un pequeño subconjunto de esos widgets espera widgets padre específicos. Cuando esta expectativa no se puede satisfacer en tu árbol de widgets, es probable que te encuentres con este error.

Aquí hay una lista incompleta de widgets que esperan widgets padre específicos dentro del framework de Flutter. Siéntete libre de enviar un PR (usando el icono de doc en la esquina superior derecha de la página) para ampliar esta lista.

WidgetWidget(s) padre esperado(s)
Flexible Row, Column o Flex
Expanded (un Flexible especializado) Row, Column o Flex
PositionedStack
TableCellTable

How to fix it?

La solución debería ser obvia una vez que sepas qué widget padre falta.

'setState llamado durante la construcción (build)'

#

El método build en tu código de Flutter no es un buen lugar para llamar a setState, ya sea directa o indirectamente.

What does the error look like?

Cuando ocurre el error, se muestra el siguiente mensaje en la consola:

The following assertion was thrown building DialogPage(dirty, dependencies:
[_InheritedTheme, _LocalizationsScope-[GlobalKey#59a8e]],
state: _DialogPageState#f121e):
setState() or markNeedsBuild() called during build.

This Overlay widget cannot be marked as needing to build because the framework
is already in the process of building widgets.
(Additional lines of this message omitted)

How might you run into the error?

En general, este error ocurre cuando el método setState es llamado dentro del método build.

Un escenario común donde ocurre este error es al intentar activar un Dialog desde dentro del método build. Esto suele estar motivado por la necesidad de mostrar información inmediatamente al usuario, pero setState nunca debería llamarse desde un método build.

El siguiente fragmento parece ser un culpable común de este error:

dart
Widget build(BuildContext context) {
  // Don't do this.
  showDialog(
    context: context,
    builder: (context) {
      return const AlertDialog(title: Text('Alert Dialog'));
    },
  );

  return const Center(
    child: Column(children: <Widget>[Text('Show Material Dialog')]),
  );
}

Este código no hace una llamada explícita a setState, pero es llamado por showDialog. El método build no es el lugar adecuado para llamar a showDialog porque build puede ser llamado por el framework para cada frame, por ejemplo, durante una animación.

How to fix it?

Una forma de evitar este error es usar la API de Navigator para activar el diálogo como una ruta (route). En el siguiente ejemplo, hay dos páginas. La segunda página tiene un diálogo para ser mostrado al entrar. Cuando el usuario solicita la segunda página al hacer clic en un botón en la primera página, el Navigator empuja (pushes) dos rutas: una para la segunda página y otra para el diálogo.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Launch screen'),
          onPressed: () {
            // Navigate to the second screen using a named route.
            Navigator.pushNamed(context, '/second');
            // Immediately show a dialog upon loading the second screen.
            Navigator.push(
              context,
              PageRouteBuilder(
                barrierDismissible: true,
                opaque: false,
                pageBuilder: (_, anim1, anim2) => const MyDialog(),
              ),
            );
          },
        ),
      ),
    );
  }
}

El ScrollController está adjunto a múltiples vistas de desplazamiento

#

Este error puede ocurrir cuando múltiples widgets de desplazamiento (como ListView) aparecen en la pantalla al mismo tiempo. Es más probable que este error ocurra en una app web o de escritorio, que en una app móvil ya que es raro encontrar este escenario en móviles.

Para obtener más información y aprender cómo solucionarlo, consulta el siguiente video sobre PrimaryScrollController:

Ver en YouTube en una nueva pestaña: "PrimaryScrollController | Decoding Flutter"

Referencias

#

Para aprender más sobre cómo depurar errores, especialmente errores de layout en Flutter, consulta los siguientes recursos: