Skip to content

Instantly share code, notes, and snippets.

@Andrious
Last active September 20, 2022 21:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Andrious/6e3e568f510c6b84cdb38b178465829d to your computer and use it in GitHub Desktop.
Save Andrious/6e3e568f510c6b84cdb38b178465829d to your computer and use it in GitHub Desktop.
An Flutter Error Handler Routine
import 'dart:async' show runZoned;
import 'dart:isolate' show Isolate, RawReceivePort;
import 'dart:ui' as ui
show
Paragraph,
ParagraphBuilder,
ParagraphConstraints,
ParagraphStyle,
TextStyle;
import 'package:flutter/foundation.dart' show FlutterExceptionHandler;
import 'package:flutter/widgets.dart' as w show runApp;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'
show
DiagnosticPropertiesBuilder,
DiagnosticsTreeStyle,
FlutterError,
FlutterErrorDetails,
FlutterExceptionHandler,
StringProperty;
import 'package:flutter/rendering.dart'
show
Color,
DiagnosticPropertiesBuilder,
EdgeInsets,
FlutterError,
Offset,
Paint,
PaintingContext,
RenderBox,
Size,
StringProperty,
TextAlign,
TextDirection;
///
///
/// typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
///
/// Example 1:
///
/// void main() => ErrorHandler.runApp(MyApp());
///
/// Example 2:
///
/// ErrorHandler.onError = (FlutterErrorDetails details) async {
/// await _reportError(details.exception, details.stack);
/// };
///
/// Example 3:
///
/// // Returns to the standard 'Red Screen'
/// ErrorHandler.builder = null;
///
class ErrorHandler {
//
ErrorHandler({
FlutterExceptionHandler handler,
ErrorWidgetBuilder builder,
}) {
_oldOnError = FlutterError.onError;
_oldBuilder = ErrorWidget.builder;
set(builder: builder, handler: handler);
}
ErrorHandler.init() {
/// Assigns a default ErrorWidget.
ErrorHandler(
builder: (FlutterErrorDetails details) => _defaultErrorWidget(details));
}
FlutterExceptionHandler _onError;
FlutterExceptionHandler _oldOnError;
ErrorWidgetBuilder _oldBuilder;
bool _inHandler = false;
static bool ranApp = false;
/// Set to null to use the 'old' handler.
set onError(FlutterExceptionHandler handler) {
// So you can assign null and use the original error routine.
_onError = handler;
set(handler: handler);
}
/// Set the ErrorWidget.builder
/// If assigned null, use the 'old' builder.
set builder(ErrorWidgetBuilder builder) {
if (builder == null) builder = _oldBuilder;
set(builder: builder);
}
void set({
Widget Function(FlutterErrorDetails details) builder,
void Function(FlutterErrorDetails details) handler,
}) {
//
if (builder != null) ErrorWidget.builder = builder;
if (handler != null) _onError = handler;
FlutterError.onError = (FlutterErrorDetails details) {
// Prevent an infinite loop and fall back to the original handler.
if (_inHandler) {
if (_onError != null && _oldOnError != null) {
_onError = null;
try {
_oldOnError(details);
} catch (ex) {
// intentionally left empty.
}
}
return;
}
// If there's an error in the error handler, we want to know about it.
_inHandler = true;
final FlutterExceptionHandler handler =
_onError == null ? _oldOnError : _onError;
if (handler != null) {
handler(details);
_inHandler = false;
}
};
}
void dispose() {
// Restore the error widget routine.
if (_oldBuilder != null) ErrorWidget.builder = _oldBuilder;
// Return the original error routine.
if (_oldOnError != null) FlutterError.onError = _oldOnError;
}
/// Determines if running in an IDE or in production.
static bool get inDebugger {
var inDebugMode = false;
// assert is removed in production.
assert(inDebugMode = true);
return inDebugMode;
}
/// Produce the FlutterErrorDetails object and invoke the FlutterError routine.
static FlutterErrorDetails _reportError(
dynamic exception,
dynamic stack,
) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack is String ? StackTrace.fromString(stack) : stack,
library: 'error_handler.dart',
);
// Call the Flutter 'onError' routine.
FlutterError.reportError(details);
// Returned to the ErrorWidget object in many cases.
return details;
}
/// Wrap the Flutter app in the Error Handler.
static void runApp(Widget myApp, [ErrorHandler handler]) {
// Can't be used properly if being called again and so do not continue.
if (ranApp) return;
ranApp = true;
// Catch any errors in the Flutter framework.
handler ??= ErrorHandler();
// Catch any errors in the main() function.
Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
var isolateError = pair as List<dynamic>;
_reportError(
isolateError.first.toString(),
isolateError.last.toString(),
);
}).sendPort);
// To catch any 'Dart' errors occurring 'outside' of the Flutter framework.
runZoned<Future<void>>(() async {
w.runApp(myApp);
// Catch any errors in the error handling.
}, onError: (error, stackTrace) {
_reportError(error, stackTrace);
});
}
}
/// This class is intentionally doing things using the low-level
/// primitives to avoid depending on any subsystems that may have ended
/// up in an unstable state -- after all, this class is mainly used when
/// things have gone wrong.
Widget _defaultErrorWidget(FlutterErrorDetails details) {
String message;
try {
message = "ERROR\n\n" + details.exception.toString() + "\n\n";
List<String> stackTrace = details.stack.toString().split("\n");
int length = stackTrace.length > 5 ? 5 : stackTrace.length;
for (var i = 0; i < length; i++) {
message += stackTrace[i] + "\n";
}
} catch (e) {
message = 'Error';
}
final Object exception = details.exception;
return _WidgetError(
message: message, error: exception is FlutterError ? exception : null);
}
class _WidgetError extends LeafRenderObjectWidget {
_WidgetError({this.message = '', FlutterError error})
: _flutterError = error,
super(key: UniqueKey());
/// The message to display.
final String message;
final FlutterError _flutterError;
@override
RenderBox createRenderObject(BuildContext context) => _ErrorBox(message);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
if (_flutterError == null)
properties.add(StringProperty('message', message, quoted: false));
else
properties.add(_flutterError.toDiagnosticsNode(
style: DiagnosticsTreeStyle.whitespace));
}
}
class _ErrorBox extends RenderBox {
///
/// A message can optionally be provided. If a message is provided, an attempt
/// will be made to render the message when the box paints.
_ErrorBox([this.message = '']) {
try {
if (message != '') {
///
/// Generally, the much better way to draw text in a RenderObject is to
/// use the TextPainter class. If you're looking for code to crib from,
/// see the paragraph.dart file and the RenderParagraph class.
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(paragraphStyle);
builder.pushStyle(textStyle);
builder.addText(message);
_paragraph = builder.build();
}
} catch (error) {
// Intentionally left empty.
}
}
/// The message to attempt to display at paint time.
final String message;
ui.Paragraph _paragraph;
@override
double computeMaxIntrinsicWidth(double height) {
return 100000.0;
}
@override
double computeMaxIntrinsicHeight(double width) {
return 100000.0;
}
@override
bool get sizedByParent => true;
@override
bool hitTestSelf(Offset position) => true;
@override
void performResize() {
size = constraints.constrain(const Size(100000.0, 100000.0));
}
/// The distance to place around the text.
///
/// This is intended to ensure that if the [RenderBox] is placed at the top left
/// of the screen, under the system's status bar, the error text is still visible in
/// the area below the status bar.
///
/// The padding is ignored if the error box is smaller than the padding.
///
/// See also:
///
/// * [minimumWidth], which controls how wide the box must be before the
// horizontal padding is applied.
static EdgeInsets padding = const EdgeInsets.fromLTRB(34.0, 96.0, 34.0, 12.0);
/// The width below which the horizontal padding is not applied.
///
/// If the left and right padding would reduce the available width to less than
/// this value, then the text is rendered flush with the left edge.
static double minimumWidth = 200.0;
/// The color to use when painting the background of [RenderBox] objects.
/// a light gray.
static Color backgroundColor = const Color(0xF0C0C0C0);
/// The text style to use when painting [RenderBox] objects.
/// a dark gray sans-serif font.
static ui.TextStyle textStyle = _initTextStyle();
static ui.TextStyle _initTextStyle() {
ui.TextStyle result = ui.TextStyle(
color: const Color(0xFF303030),
fontFamily: 'sans-serif',
fontSize: 18.0,
);
return result;
}
/// The paragraph style to use when painting [RenderBox] objects.
static ui.ParagraphStyle paragraphStyle = ui.ParagraphStyle(
textDirection: TextDirection.ltr,
textAlign: TextAlign.left,
);
@override
void paint(PaintingContext context, Offset offset) {
try {
context.canvas.drawRect(offset & size, Paint()..color = backgroundColor);
if (_paragraph != null) {
double width = size.width;
double left = 0.0;
double top = 0.0;
if (width > padding.left + minimumWidth + padding.right) {
width -= padding.left + padding.right;
left += padding.left;
}
_paragraph.layout(ui.ParagraphConstraints(width: width));
if (size.height > padding.top + _paragraph.height + padding.bottom) {
top += padding.top;
}
context.canvas.drawParagraph(_paragraph, offset + Offset(left, top));
}
} catch (e) {
// Intentionally left empty.
}
}
}
@SergeShkurko
Copy link

Thanks for your great detailed error implementation of flutter error!

null safety & flutter v3 compatible fork: https://gist.github.com/SergeShkurko/a57d5a96c405481d10db171efa287e65

@Andrious
Copy link
Author

Certainly.

Excellent update to 'null safety & Flutter v. 3'!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment