解決在 Flutter 使用 Catcher 時遇到 WidgetsFlutterBinding 初始化的問題
最近引入 Catcher 來搜集 app 發生的錯誤訊息。按照 github 上的範例修改了 main function,初始化 Catcher:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Catcher(
debugConfig: CatcherOptions(SilentReportMode(), [
ConsoleHandler(
enableApplicationParameters: false,
enableDeviceParameters: false,
enableCustomParameters: false,
enableStackTrace: true,
),
]),
releaseConfig: CatcherOptions(
SilentReportMode(),
[
HttpHandler(
HttpRequestType.post,
Uri.parse('https://x.y.z'),
enableApplicationParameters: true,
enableCustomParameters: false,
enableDeviceParameters: true,
enableStackTrace: false,
),
],
),
runAppFunction: initApp,
navigatorKey: myRootNavKey,
);
}
Future<void> initApp() async {
/*
此處省略一萬個 sdk 初始化步驟 .......
*/
runApp(const RootApp());
}
結果運行時 console 顯示錯誤:
flutter: [2023-12-29 20:47:21.680324 | Catcher | INFO] Setup localization lazily!
flutter: [2023-12-29 20:47:21.694254 | Catcher | INFO] ============================ CATCHER LOG ============================
flutter: [2023-12-29 20:47:21.694391 | Catcher | INFO] Crash occurred on 2023-12-29 20:47:21.687161
flutter: [2023-12-29 20:47:21.694430 | Catcher | INFO]
flutter: [2023-12-29 20:47:21.694508 | Catcher | INFO] ---------- ERROR ----------
flutter: [2023-12-29 20:47:21.694635 | Catcher | INFO] Zone mismatch.
The Flutter bindings were initialized in a different zone than is now being used. This will likely cause confusion and bugs as any zone-specific configuration will inconsistently use the configuration of the original binding initialization zone or this zone based on hard-to-predict factors such as which zone was active when a particular callback was set.
It is important to use the same zone when calling `ensureInitialized` on the binding as when calling `runApp` later.
To make this warning fatal, set BindingBase.debugZoneErrorsAreFatal to true before the bindings are initialized (i.e. as the first statement in `void main() { }`).
flutter: [2023-12-29 20:47:21.694679 | Catcher | INFO]
flutter: [2023-12-29 20:47:21.694899 | Catcher | INFO] ------- STACK TRACE -------
flutter: [2023-12-29 20:47:21.695005 | Catcher | INFO] #0 BindingBase.debugCheckZone.<anonymous closure> (package:flutter/src/foundation/binding.dart:503:29)
flutter: [2023-12-29 20:47:21.695041 | Catcher | INFO] #1 BindingBase.debugCheckZone (package:flutter/src/foundation/binding.dart:508:6)
flutter: [2023-12-29 20:47:21.695076 | Catcher | INFO] #2 runApp (package:flutter/src/widgets/binding.dart:1093:18)
flutter: [2023-12-29 20:47:21.695120 | Catcher | INFO] #3 initApp (package:arena/main.dart:225:3)
flutter: [2023-12-29 20:47:21.695152 | Catcher | INFO] <asynchronous suspension>
flutter: [2023-12-29 20:47:21.695183 | Catcher | INFO] #4 main.<anonymous closure>.<anonymous closure> (package:arena/main.dart:108:37)
flutter: [2023-12-29 20:47:21.695213 | Catcher | INFO] <asynchronous suspension>
flutter: [2023-12-29 20:47:21.695270 | Catcher | INFO]
flutter: [2023-12-29 20:47:21.695330 | Catcher | INFO] ======================================================================
flutter: [2023-12-29 20:47:21.695767 | Catcher | INFO] Report result: true
一番 google 後發現,這是因為 WidgetsFlutterBinding.ensureInitialized()
與 runApp
在不同的 zone 裡。
在 stackoverflow 上看到一篇相關:How do I fix this Sentry Zone mismatch error?。這篇的解法是:在 main
中運行 runZonedGuarded
。然後在 runZonedGuarded
運行 WidgetsFlutterBinding.ensureInitialized()
、Sentry 初始化以及runApp
。
但是這個方式不適合 Catcher (或是說不適合用在我的 app 裡),因為創建 Catcher 時一定要指定 runAppFunction
或者 rootWidget
其中任一個參數。然而在我的 app 裡,runApp
前有很多初始化工作,我希望一併也用 Catcher 接收錯誤訊息。所以我用不了 rootWidget
參數;而用 runAppFunction
就會遇到這個問題。
Catcher 是有 ensureInitialized
這個參數。它的說明是:should Catcher run WidgetsFlutterBinding.ensureInitialized() during initialization
。但我設定了也沒用。
看了 Flutter 官方的文件:“Zone mismatch” message。其中下面有一個程式片段:
void main() {
var myValue = Mutable<double>(0.0);
Zone.current.fork(
zoneValues: {
'myKey': myValue,
}
).run(() {
WidgetsFlutterBinding.ensureInitialized();
var newValue = ...; // obtain value from plugin
myValue.value = newValue; // update value in Zone
runApp(...);
});
}
讓我忽然有了一個靈感。
解決方法
解決方法就是:在 runAppFunction
內使用 main
的 zone 的 run
去運行 runApp
即可。
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final zone = Zone.current;
Catcher(
debugConfig: CatcherOptions(SilentReportMode(), [
ConsoleHandler(
enableApplicationParameters: false,
enableDeviceParameters: false,
enableCustomParameters: false,
enableStackTrace: true,
),
]),
releaseConfig: CatcherOptions(
SilentReportMode(),
[
HttpHandler(
HttpRequestType.post,
Uri.parse('https://x.y.z'),
enableApplicationParameters: true,
enableCustomParameters: false,
enableDeviceParameters: true,
enableStackTrace: false,
),
],
),
runAppFunction: () async => await initApp(zone),
navigatorKey: myRootNavKey,
);
}
Future<void> initApp(Zone rootZone) async {
/*
此處省略我的相關 sdk 初始化步驟
*/
rootZone.run(() => runApp(const RootApp()));
}
似乎直接在
initApp
裡面使用Zone.root.run()
也是可以的。只是不確定這個方式會不會有問題。
這樣就解決了。