Skip to main content

常见的 Flutter 错误

简介

#

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

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

#

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

红色屏幕可能出现在应用以调试或概要分析模式运行时。灰色屏幕可能出现在应用以发布模式运行时。

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

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

“RenderFlex 溢出…”

#

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

错误是什么样的?

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

布局期间抛出以下断言:
RenderFlex 向右溢出 1146 像素。

相关的导致错误的小部件是

    Row      lib/errors/renderflex_overflow_column.dart:23

溢出的 RenderFlex 的方向为 Axis.horizontal。
溢出的 RenderFlex 边缘已在渲染中用黄色和黑色条纹图案标记。这通常是由内容太大而无法容纳在 RenderFlex 中引起的。
(省略此消息的其他行)

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

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(
          // 代码省略
          ),
    ),
  ],
);

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

更多信息:

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

“RenderBox 未布局”

#

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

错误是什么样的?

错误显示的消息如下所示:

RenderBox 未布局:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE

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

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

RenderBox 未布局 错误通常是由以下两个错误之一引起的:

  • “垂直视口被赋予了无限高度”
  • “InputDecorator……不能具有无限宽度”

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

#

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

错误是什么样的?

错误显示的消息如下所示:

performResize() 期间抛出以下断言:
垂直视口被赋予了无限高度。

视口在滚动方向上展开以填充其容器。
在这种情况下,垂直视口被赋予了无限量的垂直空间来展开。这种情况通常发生在可滚动小部件嵌套在另一个可滚动小部件内时。
(省略此消息的其他行)

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

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 框架错误非常重要。

错误是什么样的?

错误显示的消息如下所示:

performLayout() 期间抛出以下断言:
InputDecorator(通常由 TextField 创建)不能具有无限宽度。
当父小部件未提供有限宽度约束时,就会发生这种情况。例如,如果 InputDecorator 包含在 `Row` 中,则其宽度必须受约束。可以使用 `Expanded` 小部件或 SizedBox 来约束 InputDecorator 或包含它的 TextField 的宽度。
(省略此消息的其他行)

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

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

dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('TextField 的无限宽度'),
      ),
      body: const Row(
        children: [
          TextField(),
        ],
      ),
    ),
  );
}

如何修复?

如错误消息所示, 使用 ExpandedSizedBox 小部件约束文本字段来修复此错误。以下示例演示了如何使用 Expanded 小部件:

dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('TextField 的无限宽度'),
      ),
      body: Row(
        children: [
          Expanded(child: TextFormField()),
        ],
      ),
    ),
  );
}

“不正确地使用了 ParentData 小部件”

#

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

错误是什么样的?

错误显示的消息如下所示:

查找父数据时抛出以下断言:
不正确地使用了 ParentDataWidget。
(此消息的某些行已省略)
通常,这表示上面列出的至少一个有问题的 ParentDataWidgets 没有直接放在兼容的祖先小部件内。

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

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

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

小部件预期的父小部件(s)
FlexibleRowColumnFlex
Expanded(一个专门的 FlexibleRowColumnFlex
PositionedStack
TableCellTable

如何修复?

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

“在 build 期间调用了 setState”

#

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

错误是什么样的?

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

构建 DialogPage(dirty, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#59a8e]], state: _DialogPageState#f121e) 时抛出以下断言:
在 build 期间调用了 setState() 或 markNeedsBuild()。

此 Overlay 小部件不能标记为需要构建,因为框架已经在构建小部件的过程中。
(此消息的其他行已省略)

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

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

发生此错误的一个常见场景是尝试从 build 方法中触发 Dialog。这通常是由于需要立即向用户显示信息,但是 setState 绝不应该从 build 方法中调用。

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

dart
Widget build(BuildContext context) {
  // 不要这样做。
  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: () {
            // 使用命名路由导航到第二个屏幕。
            Navigator.pushNamed(context, '/second');
            // 在加载第二个屏幕时立即显示对话框。
            Navigator.push(
              context,
              PageRouteBuilder(
                barrierDismissible: true,
                opaque: false,
                pageBuilder: (_, anim1, anim2) => const MyDialog(),
              ),
            );
          },
        ),
      ),
    );
  }
}

ScrollController 附加到多个滚动视图

#

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

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


参考

#

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