Flutter 能夠自動處理在其內部運作過程中出現的錯誤。比如渲染畫面、安排組件位置、建立使用者介面時,如果出現錯誤,Flutter 會自動攔截這些錯誤。但如果錯誤發生在框架管控的範圍之外,例如發送網路請求時或者讀取檔案時出錯,此時 Flutter 就無法自動攔截 catch 這些問題。但我們可以使用 PlatformDispatcher進行錯誤處理。
所有自動攔截到的錯誤都會被導向 FlutterError.onError
,預設情況下會呼叫 FlutterError.presentError
方法將錯誤訊息輸出到裝置的日誌中。
但當從 IDE 執行時,情況不太一樣,IDE Inspector 會覆寫這個預設行為,讓錯誤也被導向到編輯器的控制台。如此一來開發者可以更方便的查詢這些錯誤資訊。
當錯誤是在建構時期發生的話會呼叫 ErrorWidget.builder
來建立一個替代性的組件,代替那個出錯的組件。在偵錯模式下,替代組件預設會顯示紅色錯誤訊息,在正式模式(Release mode) 則會顯示一個灰色背景。
其他 Flutter 管控範圍外的錯誤,會在 PlatfromDispatcher
的 error callback 中處理,預設只會輸出錯誤訊息。
我們可以自訂這些行為,一般是在 void main()
裡面設定。 下面我們將介紹各種錯誤類型。
若要讓你的應用程式在發布模式下,每當 Flutter 攔截到錯誤時立刻退出,你可以使用以下的處理程序:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
FlutterError.onError = (details) {
FlutterError.presentError(details);
if (kReleaseMode) {
exit(1);
}
runApp(const MyApp());
}
}
常量 kReleaseMode
用於判斷應用程式是否以發布模式編譯。這個處理程序也可以用來將錯誤回報給日誌記錄服務。
為了自訂錯誤組件用於在 builder
無法建立組件時顯示,可以使用 MaterialApp.builder
:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, widget) {
Widget error = const Text('...rendering error...');
if (widget is Scaffold || widget is Navigator) {
error = Scaffold(body: Center(child: error));
}
ErrorWidget.builder = (errorDetails) => error;
if (widget != null) return widget;
throw StateError('widget is null');
},
);
}
}
另一種常見的情況比如所在 onPressed
中呼叫了非同步函式:
OutlinedButton(
child: const Text('Click me!'),
onPressed: () async {
const channel = MethodChannel('crashy-custom-channel');
await channel.invokeMethod('blah');
},
)
若 invokeMethod
拋出例外,此時 FlutterError.onError
無法自動攔截。這個使用需要使用 PlatformDispatcher.instance.onError
import 'package:flutter/material.dart';
import 'dart:ui';
void main() {
MyBackend myBackend = MyBackend();
PlatformDispatcher.instance.onError = (error, stack) {
myBackend.sendError(error, stack);
return true;
};
runApp(const MyApp());
}
假設你希望在任何異常發生時退出應用程式,並且在組件建構失敗時顯示一個自訂的錯誤組件 - 你可以基於以下程式碼片段來處理錯誤:
import 'package:flutter/material.dart';
import 'dart:ui';
Future<void> main() async {
await myErrorsHandler.initialize();
FlutterError.onError = (details) {
FlutterError.presentError(details);
myErrorsHandler.onErrorDetails(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
myErrorsHandler.onError(error, stack);
return true;
};
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, widget) {
Widget error = const Text('...rendering error...');
if (widget is Scaffold || widget is Navigator) {
error = Scaffold(body: Center(child: error));
}
ErrorWidget.builder = (errorDetails) => error;
if (widget != null) return widget;
throw StateError('widget is null');
},
);
}
}
若您已經使用了 BLoC 等狀態管理你後續可以參考 How I handle errors in Flutter。