跳至主要内容

Flutter 常用错误

简介

#

本页面解释了几个经常遇到的 Flutter 框架错误(包括布局错误),并提供了解决它们的建议。这是一个动态文档,未来版本中将添加更多错误,欢迎您的贡献。请随时提交问题提交拉取请求,使此页面对您和 Flutter 社区更有用。

运行应用程序时出现纯红色或灰色屏幕

#

通常称为“红色(或灰色)死亡屏幕”,这有时是 Flutter 通知您存在错误的方式。

当应用程序在调试或概要分析模式下运行时,可能会出现红色屏幕。当应用程序在发布模式下运行时,可能会出现灰色屏幕。

通常,这些错误发生在出现未捕获的异常(您可能需要另一个 try-catch 块)或出现某些渲染错误(例如溢出错误)时。

以下文章提供了一些关于调试此类错误的有用见解

'RenderFlex 溢出…'

#

RenderFlex 溢出是 Flutter 框架中最常遇到的错误之一,您可能已经遇到过。

错误是什么样子的?

发生这种情况时,会出现黄色和黑色条纹,指示应用程序 UI 中溢出的区域。此外,调试控制台中还会显示错误消息。

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)

您可能会如何遇到此错误?

此错误通常发生在 ColumnRow 的子小部件的大小未受约束时。例如,以下代码片段演示了一个常见场景

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.',
          ),
        ],
      ),
    ],
  );
}

在上面的示例中,Column 试图比 Row(其父级)可以分配给它的空间更宽,从而导致溢出错误。为什么 Column 会尝试这样做?要理解这种布局行为,您需要了解 Flutter 框架如何执行布局。

"为了执行布局,Flutter 以深度优先遍历的方式遍历渲染树,并向下传递大小约束从父级到子级…子级通过向上传递大小在其父级对象中,在父级建立的约束范围内。" – Flutter 架构概述

在这种情况下,Row 小部件不会约束其子级的大小,Column 小部件也不会。由于缺少来自其父级小部件的约束,第二个 Text 小部件试图与其需要显示的所有字符一样宽。然后,Text 小部件的自确定宽度会被 Column 采用,这与它的父级 Row 小部件所能提供的最大水平空间发生冲突。

如何解决?

好吧,您需要确保 Column 不会尝试比它可以容纳的更宽。要实现这一点,您需要约束其宽度。一种方法是用 Expanded 小部件包装 Column

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

另一种方法是用 Flexible 小部件包装 Column 并指定 flex 因子。事实上,Expanded 小部件等效于 flex 因子为 1.0 的 Flexible 小部件,如其源代码所示。要进一步了解如何在 Flutter 布局中使用 Flex 小部件,请查看此 90 秒的每周小部件视频关于 Flexible 小部件。

更多信息

下面链接的资源提供了有关此错误的更多信息。

'RenderBox 未进行布局'

#

虽然此错误很常见,但它通常是渲染管道早期发生的根本错误的副作用。

错误是什么样子的?

错误显示的消息如下所示

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

您可能会如何遇到此错误?

通常,问题与违反盒子约束有关,需要通过为 Flutter 提供更多信息来解决,说明您希望如何约束相关的小部件。您可以在理解约束页面上详细了解约束在 Flutter 中的工作原理。

RenderBox 未进行布局错误通常由以下两个错误之一引起

  • '垂直视口被赋予了无限高度'
  • 'InputDecorator...不能具有无限宽度'

'垂直视口被赋予了无限高度'

#

这是另一个在 Flutter 应用程序中创建 UI 时可能遇到的常见布局错误。

错误是什么样子的?

错误显示的消息如下所示

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)

您可能会如何遇到此错误?

此错误通常发生在将 ListView(或其他类型的可滚动小部件,例如 GridView)放置在 Column 中时。ListView 会占用所有可用的垂直空间,除非它受到其父级小部件的约束。但是,Column 默认情况下不会对其子级的高度施加任何约束。这两种行为的组合导致无法确定 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'),
            ),
          ],
        ),
      ],
    ),
  );
}

如何解决?

要修复此错误,请指定 ListView 的高度。要使其与 Column 中剩余的空间一样高,请使用 Expanded 小部件将其包装(如以下示例所示)。否则,请使用 SizedBox 小部件指定绝对高度或使用 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'),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

更多信息

下面链接的资源提供了有关此错误的更多信息。

'InputDecorator...不能具有无限宽度'

#

错误消息表明它也与盒子约束有关,理解盒子约束对于避免许多最常见的 Flutter 框架错误非常重要。

错误是什么样子的?

错误显示的消息如下所示

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)

您可能会如何遇到此错误?

例如,当 Row 包含 TextFormFieldTextField 但后者没有宽度约束时,就会发生此错误。

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

如何解决?

如错误消息所示,通过使用 ExpandedSizedBox 小部件约束文本字段来修复此错误。以下示例演示了如何使用 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()),
        ],
      ),
    ),
  );
}

'ParentData 小部件使用不正确'

#

此错误与缺少预期的父级小部件有关。

错误是什么样子的?

错误显示的消息如下所示

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.

您可能会如何遇到此错误?

虽然 Flutter 的小部件在 UI 中组合的方式通常很灵活,但其中一小部分小部件期望特定的父级小部件。当您的小部件树中无法满足此期望时,您很可能会遇到此错误。

以下是 Flutter 框架中期望特定父级小部件的小部件的不完整列表。请随时提交 PR(使用页面右上角的文档图标)来扩展此列表。

小部件期望的父级小部件
FlexibleRowColumnFlex
Expanded(一个专门的 FlexibleRowColumnFlex
PositionedStack
TableCellTable

如何解决?

一旦知道缺少哪个父级小部件,修复方法应该很明显。

'在构建过程中调用 setState'

#

Flutter 代码中的 build 方法不是直接或间接调用 setState 的好地方。

错误是什么样子的?

发生错误时,控制台中会显示以下消息

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)

您可能会如何遇到此错误?

通常,此错误发生在 build 方法内调用 setState 方法时。

此错误发生的常见场景是从 build 方法内触发 Dialog。这通常是出于需要立即向用户显示信息的动机,但永远不要从 build 方法中调用 setState

以下代码片段似乎是此错误的常见罪魁祸首

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'),
      ],
    ),
  );
}

此代码没有显式调用 setState,但它是由 showDialog 调用的。build 方法不是调用 showDialog 的正确位置,因为 build 可以由框架为每一帧调用,例如在动画期间。

如何解决?

避免此错误的一种方法是使用 Navigator API 将对话框作为路由触发。在以下示例中,有两个页面。第二个页面有一个在进入时要显示的对话框。当用户通过单击第一个页面上的按钮请求第二个页面时,Navigator 会推送两个路由——一个用于第二个页面,另一个用于对话框。

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(),
              ),
            );
          },
        ),
      ),
    );
  }
}

ScrollController 附加到多个滚动视图

#

当多个滚动小部件(如 ListView)同时出现在屏幕上时,可能会发生此错误。在 Web 或桌面应用程序上发生此错误的可能性高于移动应用程序,因为在移动设备上很少遇到这种情况。

有关更多信息以及如何修复,请查看以下关于PrimaryScrollController的视频


参考

#

要了解有关如何调试错误的更多信息,尤其是在 Flutter 中调试布局错误,请查看以下资源