常見 Flutter 錯誤
介紹
#此頁面解釋了幾個經常遇到的 Flutter 框架錯誤(包括佈局錯誤)並提供瞭解決它們的建議。這是一個動態文件,將在未來的修訂版中新增更多錯誤,並且歡迎您的貢獻。歡迎您 開啟一個 issue 或 提交一個 pull request,以使此頁面對您和 Flutter 社群更有用。
執行應用時出現純紅色或灰色螢幕
#通常被稱為“紅色(或灰色)死亡螢幕”,有時 Flutter 會透過這種方式告知您存在錯誤。
當應用程式在除錯或 profile 模式下執行時,可能會出現紅色螢幕。當應用程式在 release 模式下執行時,可能會出現灰色螢幕。
通常,這些錯誤發生在未捕獲的異常(您可能需要另一個 try-catch 塊)或某些渲染錯誤(如溢位錯誤)時。
以下文章提供了一些關於除錯此類錯誤的有用見解
- Abishek 撰寫的 Flutter 錯誤詳解
- Christopher Nwosu-Madueke 撰寫的 理解和解決 Flutter 中的灰色螢幕
- Kesar Bhimani 撰寫的 Flutter 卡在白屏
'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)您可能會如何遇到此錯誤?
當 Column 或 Row 包含一個大小未受限的子控制元件時,通常會發生此錯誤。例如,以下程式碼片段展示了一個常見場景
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 不會嘗試比其可用空間更寬。要實現這一點,您需要限制其寬度。一種方法是將 Column 包裝在 Expanded 控制元件中。
return const Row(
children: [
Icon(Icons.message),
Expanded(
child: Column(
// code omitted
),
),
],
);另一種方法是將 Column 包裝在 Flexible 控制元件中並指定一個 flex 因子。事實上,Expanded 控制元件等同於 flex 因子為 1.0 的 Flexible 控制元件,正如其 原始碼 所顯示的那樣。要進一步瞭解如何在 Flutter 佈局中使用 Flex 控制元件,請檢視 這個 90 秒的 Widget of the Week 影片,該影片介紹了 Flexible 控制元件。
更多資訊
以下連結的資源提供了有關此錯誤的更多資訊。
'RenderBox 未進行佈局'
#雖然此錯誤很常見,但它通常是渲染管道早期發生的某個主要錯誤的副作用。
錯誤看起來是怎樣的?
錯誤顯示的訊息如下所示
RenderBox was not laid out:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE您可能會如何遇到此錯誤?
通常,該問題與違反盒子約束有關,需要透過向 Flutter 提供有關您希望如何約束相關控制元件的更多資訊來解決。您可以在 理解約束 頁面上了解有關 Flutter 中約束工作原理的更多資訊。
RenderBox 未進行佈局 錯誤通常由以下兩個其他錯誤之一引起
- 'Vertical viewport 被賦予了無界高度'
- 'InputDecorator… 無法擁有無界寬度'
'Vertical viewport 被賦予了無界高度'
#這是您在 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 的大小。
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 控制元件指定相對高度。
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 包含 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 控制元件來約束文字欄位來修復此錯誤。以下示例演示了使用 Expanded 控制元件。
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(使用頁面右上角的 doc 圖示)來擴充套件此列表。
| 小部件 | 期望的父控制元件 |
|---|---|
Flexible | Row、Column 或 Flex |
Expanded(一個特殊的 Flexible) | Row、Column 或 Flex |
Positioned | 層疊佈局 |
TableCell | Table |
如何修復?
一旦您知道缺少哪個父控制元件,修復應該就很明顯了。
'setState 在 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)您可能會如何遇到此錯誤?
總的來說,當 setState 方法在 build 方法中被呼叫時,就會發生此錯誤。
此錯誤發生的一個常見場景是在 build 方法中嘗試觸發 Dialog。這通常是因為需要立即向用戶顯示資訊,但 setState 永遠不應從 build 方法呼叫。
以下程式碼片段似乎是此錯誤的常見原因
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 已附加到多個滾動檢視
#當螢幕上同時出現多個滾動控制元件(如 ListView)時,可能會發生此錯誤。此錯誤在 Web 或桌面應用上比在移動應用上發生的可能性更大,因為在移動裝置上很少遇到這種情況。
有關更多資訊以及如何修復,請檢視以下關於 PrimaryScrollController 的影片
在 YouTube 上在新標籤頁中觀看:“PrimaryScrollController | Decoding Flutter”
參考資料
#要了解有關如何除錯錯誤(尤其是在 Flutter 中進行佈局錯誤)的更多資訊,請檢視以下資源