常见 Flutter 错误
介绍
#本页解释了几种常见的 Flutter 框架错误(包括布局错误),并提供了解决方法。这是一份持续更新的文档,未来将添加更多错误,欢迎您的贡献。您可以随时提交问题或提交拉取请求,让本页面对您和 Flutter 社区更有用。
运行应用时出现的红色或灰色实心屏幕
#这通常被称为“红色(或灰色)死亡屏幕”,有时是 Flutter 提示您存在错误的方式。
红色屏幕在应用以调试或配置文件模式运行时出现。灰色屏幕在应用以发布模式运行时出现。
通常,这些错误发生在存在未捕获的异常(您可能需要另一个 try-catch 块),或者存在某些渲染错误,例如溢出错误时。
以下文章提供了关于调试此类错误的一些有用见解:
- Abishek 的揭秘 Flutter 错误
- Christopher Nwosu-Madueke 的理解和解决 Flutter 中的灰色屏幕
- Kesar Bhimani 的Flutter 卡在白屏
'A RenderFlex overflowed…'
#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)
您可能如何遇到此错误?
当 Column
或 Row
的子 widget 大小不受约束时,通常会发生此错误。例如,以下代码片段演示了一个常见场景:
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
widget 不会约束其子级的大小,Column
widget 也是如此。由于缺少来自其父 widget 的约束,第二个 Text
widget 试图变得与其需要显示的字符宽度一样宽。Text
widget 的自定宽度随后被 Column
采用,这与其父级 Row
widget 可以提供的最大水平空间发生冲突。
如何修复?
嗯,您需要确保 Column
不会尝试比它应有的宽度更宽。为此,您需要约束其宽度。一种方法是将 Column
包装在 Expanded
widget 中。
return const Row(
children: [
Icon(Icons.message),
Expanded(
child: Column(
// code omitted
),
),
],
);
另一种方法是将 Column
包装在 Flexible
widget 中并指定 flex
因子。事实上,Expanded
widget 等同于 flex
因子为 1.0 的 Flexible
widget,正如其源代码所示。要进一步了解如何在 Flutter 布局中使用 Flex
widget,请查看关于 Flexible
widget 的这个 90 秒的“本周 widget”视频。
更多信息
以下链接的资源提供了关于此错误的更多信息。
'RenderBox was not laid out'
#虽然此错误很常见,但它通常是渲染管道早期发生的某个主要错误的副作用。
错误是什么样子的?
错误显示的消息如下:
RenderBox was not laid out:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
您可能如何遇到此错误?
通常,问题与违反箱形约束有关,需要通过向 Flutter 提供更多关于您希望如何约束相关 widget 的信息来解决。您可以在理解约束页面上了解更多关于 Flutter 中约束如何工作的信息。
RenderBox was not laid out
错误通常由以下两种其他错误之一引起:
- 'Vertical viewport was given unbounded height'
- 'An InputDecorator...cannot have an unbounded width'
'Vertical viewport was given unbounded height'
#这是您在 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
(或其他类型的可滚动 widget,如 GridView
)放置在 Column
内部时,通常会引起此错误。除非其父 widget 约束其大小,否则 ListView
会占用所有可用的垂直空间。然而,Column
默认不对其子 widget 的高度施加任何约束。这两种行为的结合导致无法确定 ListView
的大小。
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
widget 包装它(如以下示例所示)。否则,请使用 SizedBox
widget 指定绝对高度,或使用 Flexible
widget 指定相对高度。
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')),
],
),
),
],
),
);
}
更多信息
以下链接的资源提供了关于此错误的更多信息。
'An InputDecorator...cannot have an unbounded width'
#错误消息表明它也与箱形约束有关,理解这些约束对于避免许多最常见的 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
包含 TextFormField
或 TextField
而后者没有宽度约束时,会发生此错误。
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
body: const Row(children: [TextField()]),
),
);
}
如何修复?
如错误消息所示,通过使用 Expanded
或 SizedBox
widget 约束文本字段来修复此错误。以下示例演示了使用 Expanded
widget:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
body: Row(children: [Expanded(child: TextFormField())]),
),
);
}
'Incorrect use of ParentData widget'
#此错误是关于缺少预期父 widget 的。
错误是什么样子的?
错误显示的消息如下:
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 的 widget 在 UI 中组合方式通常很灵活,但其中一小部分 widget 需要特定的父 widget。当您的 widget 树中无法满足此预期时,您很可能会遇到此错误。
以下是 Flutter 框架中需要特定父 widget 的不完整列表。欢迎提交 PR(使用页面右上角的文档图标)来扩展此列表。
小部件 | 预期父 widget |
---|---|
Flexible | Row 、Column 或 Flex |
Expanded (一种特殊的 Flexible ) | Row 、Column 或 Flex |
Positioned | 层叠布局 |
TableCell | Table |
如何修复?
一旦您知道缺少哪个父 widget,修复方法应该就很明显了。
'setState called during build'
#您的 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
。
以下代码片段似乎是此错误的常见原因:
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
会推送两个路由——一个用于第二页,另一个用于对话框。
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 附加到多个可滚动视图
#当多个可滚动 widget(例如 ListView
)同时出现在屏幕上时,可能会发生此错误。此错误更有可能发生在 Web 或桌面应用上,而不是移动应用上,因为在移动设备上很少遇到这种情况。
要了解更多信息以及如何修复,请查看关于 PrimaryScrollController
的以下视频:
参考资料
#要了解更多关于如何调试错误,尤其是 Flutter 中的布局错误,请查看以下资源: