Boundary
Boundary 是 Flutter 的一个新 widget,它接管 FlutterError.onError 和 ErrorWidget.builder,使它们可以组合和作用域化。
如果您曾想过将错误报告仅应用于 widget 树的特定部分,或者发现实现“糟糕”/加载屏幕很困难,那么这个库非常适合您。
安装
为了让 Boundary 生效,必须先调用 setupBoundary。
这可以在您的 main 函数中完成,如下所示
void main() {
setupBoundary();
runApp(MyApp());
}
出于测试目的,setupBoundary 返回一个函数以恢复设置
到它们的默认行为
testWidgets('mytest', (tester) async {
final restore = setupBoundary();
await tester.pumpWidget(
Boundary(
fallback: (_, __) => Container(),
child: Text('foo', textDirection: TextDirection.ltr),
)
);
// necessary call before any `expect`, otherwise the test framework will throw
restore();
expect(find.text('foo'), findsOneWidget);
});
原理
错误报告和备用 UI 现在通过一个通用的 widget 来表示
Boundary
当这个 widget 插入到 widget 树中时,它能够捕获异常
来自其后代(仅后代),然后创建一个备用 UI。
这是一个典型的例子
Scaffold(
appBar: AppBar(title: const Text('hello')),
body: Boundary(
fallbackBuilder: (context, error) {
return const Center(child: Text('Oops'));
},
child: Container(
color: Colors.red,
padding: const EdgeInsets.all(50),
child: Builder(builder: (_) {
// a descendant somethow failed
throw 42;
}),
),
),
);
其渲染效果如下

请注意,即使有一个 Container 带有 padding 和红色背景
作为 Boundary 的子项,“糟糕”屏幕不显示任何这些内容
fallbackBuilder 返回的 widget 在一个完全不同的 widget 树中。
但是,失败的子树(Container -> Builder)也没有从树中移除!
它的状态得以保留,并且只是被移出屏幕,直到它成功重建。
这一点通过 以下示例 得到证明,该示例展示了 Boundary 如何使用
来显示 widget 树中更深的 FutureBuilder 的加载/错误屏幕
– 而无需引用 Future。
Boundary(
fallbackBuilder: (_, error) {
// doesn't have the reference on the Future, but
// is still able to display loading/error state
if (error is Loading) {
return const Center(child: CircularProgressIndicator());
} else if (error is NotFoundError) {
return const NotFoundScreen();
} else {
return const OopsScreen();
}
},
child: SubtreeThatHasAFutureBuilder(),
)

常见问题
如何移除备用屏幕
一旦抛出异常,就会显示备用屏幕。但您可能想要
在某个时候停止显示该备用项。
要实现这一点,只需重建失败的 widget,使其不再抛出异常
即可。这将自动移除备用屏幕。
如果 fallbackBuilder 中出现异常会怎样?
如果在 fallbackBuilder 中出现异常,则该异常会被传播
到下一个 Boundary,直到没有为止。
Boundary(
fallbackBuilder: (_, err) => Text(err.toString()),
child: Boundary(
fallbackBuilder: (_, err) {
print(err);
throw err;
},
child: Builder(builder: (_) {
throw 42;
})
)
)
使用前面的片段,这将在控制台首先打印 42,然后
在屏幕上渲染一个带有“42”的 Text。