-
-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Catch Rendering Errors #1090
Comments
For widgets that support error reporting, you can handle it in the |
Hi, I already tried the library you mentioned, it's super outdated and I couldn't get it to work. Could you please show me how I can handle the SVG error myself? I tried flutter onError, but that doesn't seem to catch it. |
I fixed NNBD issues for the lib but it didn't work...import 'package:flutter/widgets.dart';
typedef BoundaryWidgetBuilder = Widget Function(
BuildContext context,
dynamic error,
);
/// No `Boundary<valueType>` were found as an ancestor of a [Defer] was used.
class BoundaryNotFoundError extends Error {
/// The type of the widget that tried (and failed) to access `Boundary<valueType>`.
final Type valueType;
/// Allows specifying [valueType]
BoundaryNotFoundError(this.valueType);
@override
String toString() {
return '''
Error: No Boundary<$valueType> found.
''';
}
}
class _InheritedBoundary extends InheritedWidget {
const _InheritedBoundary({
this.element,
required super.child,
});
static _BoundaryElement? of(BuildContext context) {
final widget = context
.getElementForInheritedWidgetOfExactType<_InheritedBoundary>()
?.widget;
if (widget is _InheritedBoundary) {
return widget.element;
}
return null;
}
final _BoundaryElement? element;
@override
bool updateShouldNotify(_InheritedBoundary oldWidget) {
return oldWidget.element != element;
}
}
/// Defines a fallback behavior for [Defer] widget used inside `child`.
class Boundary extends StatelessWidget {
/// [fallbackBuilder] and [child] must not be `null`.
const Boundary({
super.key,
required this.fallbackBuilder,
required this.child,
});
/// The subtree from which [Boundary] will capture errors.
///
/// If [child] or any of its descendants throws when building the widget,
/// then [fallbackBuilder] will be called with the exception.
final Widget child;
/// A callback used to create a fallback UI if [child] fails to build.
///
/// It is fine for [fallbackBuilder] to throws too, in which case the error
/// will be propagated to other boundaries.
final BoundaryWidgetBuilder fallbackBuilder;
@override
_BoundaryElement createElement() => _BoundaryElement(this);
@override
Widget build(BuildContext context) {
return _Internal(
showChild: true,
element: context as _BoundaryElement,
exception: context.exception,
fallbackBuilder: fallbackBuilder,
child: child,
);
}
}
/// A widget that asks the nearest [Boundary] in its ancestors to call
/// [Boundary.fallbackBuilder].
class Defer extends StatelessWidget {
/// Allows specifying [details].
const Defer(this.details, {super.key});
/// The object that will be passed as parameter to [Boundary.fallbackBuilder].
final Object details;
@override
_FallbackElement createElement() => _FallbackElement(this);
@override
Widget build(BuildContext context) => const SizedBox();
}
class _FallbackElement extends StatelessElement {
_FallbackElement(Defer super.widget);
@override
Defer get widget => super.widget as Defer;
_BoundaryElement? boundary;
_BoundaryElement? didCatch;
@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
final res = super.updateChild(child, newWidget, newSlot);
final b = boundary = _InheritedBoundary.of(this);
if (b == null) {
FlutterError.reportError(
FlutterErrorDetails(
library: 'boundary',
exception: BoundaryNotFoundError(widget.details.runtimeType),
),
);
} else {
b.markSubtreeFailed(widget.details);
}
return res;
}
@override
void deactivate() {
final b = boundary;
if (b != null && b.activated) {
b.markSubtreeFailed(null);
}
super.deactivate();
}
}
class _Internal extends StatelessWidget {
const _Internal({
required this.child,
required this.element,
this.exception,
required this.fallbackBuilder,
required this.showChild,
});
final _BoundaryElement element;
final Widget child;
final BoundaryWidgetBuilder fallbackBuilder;
final dynamic exception;
final bool showChild;
@override
Widget build(BuildContext context) {
if (exception != null) {
if (!showChild) {
element.errorWidget = _InheritedBoundary(
element: _InheritedBoundary.of(context),
child: Builder(
builder: (context) => fallbackBuilder(context, exception),
),
);
}
} else {
element.errorWidget = null;
}
final valid = Offstage(
offstage: !showChild && exception != null,
child: child,
);
final errorWidget = element.errorWidget;
return _InheritedBoundary(
element: element,
child: Stack(
fit: StackFit.passthrough,
alignment: Alignment.center,
children: [valid, if (errorWidget != null) errorWidget],
),
);
}
}
class _BoundaryElement extends StatelessElement {
_BoundaryElement(Boundary super.widget);
@override
Boundary get widget => super.widget as Boundary;
Object? failure;
bool isBuilding = false;
bool activated = false;
dynamic exception;
Widget? errorWidget;
Element? _child;
dynamic _slot;
@override
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
_child ??= child;
_slot = newSlot;
return _child = super.updateChild(child, newWidget, newSlot);
}
@override
void performRebuild() {
isBuilding = true;
final hadError = failure != null;
failure = null;
super.performRebuild();
isBuilding = false;
final hasError = failure != null;
if (hasError != hadError) {
exception = failure;
rebuildWithError(exception);
}
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
activated = true;
}
@override
void activate() {
super.activate();
activated = true;
}
@override
void deactivate() {
activated = false;
super.deactivate();
}
void markSubtreeFailed(Object? failure) {
this.failure = failure;
exception = failure;
if (!isBuilding) {
rebuildWithError(exception);
}
}
void rebuildWithError(dynamic exception) {
updateChild(
_child,
_Internal(
element: this,
showChild: false,
fallbackBuilder: widget.fallbackBuilder,
exception: exception,
child: widget.child,
),
_slot,
);
}
} Turned out the I guess we will have to wait for error builder to be included in |
Thank you for taking the time to investigate the problem, I appreciate it. Let's hope the fix will be implemented soon! |
Hello, thanks a lot for the time you've put into this package. I was wondering if it would be possible to know and catch if any errors occur during rendering.
Use case
I'm rendering content that comes in from a source where I have no control. Sometimes there are SVG tags that do not have dimensions properly specified, which causes the SVG library to thrown an exeption and crash the whole application.
For example:
The inner svg tag does not have dimensions specified and this throws the following error:
Proposal
Would it be possible to implement a way to to detect and catch errors at runtime so that they can be properly handled?
Thanks
The text was updated successfully, but these errors were encountered: