diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c0b6245..fad1d3f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog -## [15.0.2](https://github.com/Instabug/Instabug-Flutter/compare/v14.3.0...15.0.1) (Jul 7, 2025) +## [Unreleased](https://github.com/Instabug/Instabug-Flutter/compare/v15.0.1...dev) + +### Added + +- Added `InstabugWidget`, a wrapper used to wrap the main application to provide out of the box automatic Crash Reporting support. ([#598](https://github.com/Instabug/Instabug-Flutter/pull/598)) + +## [15.0.2](https://github.com/Instabug/Instabug-Flutter/compare/v14.3.0...15.0.1) ### Added diff --git a/example/lib/main.dart b/example/lib/main.dart index 91b0a67e7..51d712663 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter/src/utils/instabug_widget.dart'; import 'package:instabug_flutter_example/src/components/apm_switch.dart'; import 'package:instabug_http_client/instabug_http_client.dart'; import 'package:instabug_flutter_example/src/app_routes.dart'; @@ -43,24 +44,19 @@ part 'src/components/traces_content.dart'; part 'src/components/flows_content.dart'; void main() { - runZonedGuarded( - () { - WidgetsFlutterBinding.ensureInitialized(); - - Instabug.init( - token: 'ed6f659591566da19b67857e1b9d40ab', - invocationEvents: [InvocationEvent.floatingButton], - debugLogsLevel: LogLevel.verbose, - ); - - FlutterError.onError = (FlutterErrorDetails details) { - Zone.current.handleUncaughtError(details.exception, details.stack!); - }; - - runApp(const MyApp()); - }, - CrashReporting.reportCrash, + WidgetsFlutterBinding.ensureInitialized(); + + Instabug.init( + token: 'ed6f659591566da19b67857e1b9d40ab', + invocationEvents: [InvocationEvent.floatingButton], + debugLogsLevel: LogLevel.verbose, + ); + + final app = InstabugWidget( + child: const MyApp(), ); + + runApp(app); } class MyApp extends StatelessWidget { diff --git a/example/lib/src/components/non_fatal_crashes_content.dart b/example/lib/src/components/non_fatal_crashes_content.dart index c3d331187..3f4b06da7 100644 --- a/example/lib/src/components/non_fatal_crashes_content.dart +++ b/example/lib/src/components/non_fatal_crashes_content.dart @@ -21,6 +21,10 @@ class _NonFatalCrashesContentState extends State { log('throwHandledException: Crash report for ${err.runtimeType} is Sent!', name: 'NonFatalCrashesWidget'); } + CrashReporting.reportHandledCrash( + err, + StackTrace.current, + ); } } diff --git a/lib/src/utils/instabug_widget.dart b/lib/src/utils/instabug_widget.dart new file mode 100644 index 000000000..e2c7ae271 --- /dev/null +++ b/lib/src/utils/instabug_widget.dart @@ -0,0 +1,147 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:instabug_flutter/src/modules/crash_reporting.dart'; +import 'package:instabug_flutter/src/utils/instabug_logger.dart'; + +class InstabugWidget extends StatefulWidget { + final Widget child; + + /// Custom handler for Flutter errors. + /// + /// This callback is called when a Flutter error occurs. It receives a + /// [FlutterErrorDetails] object containing information about the error. + /// + /// Example: + /// ```dart + /// InstabugWidget( + /// flutterErrorHandler: (details) { + /// print('Flutter error: ${details.exception}'); + /// // Custom error handling logic + /// }, + /// child: MyApp(), + /// ) + /// ``` + /// + /// Note: If this handler throws an error, it will be caught and logged + /// to prevent it from interfering with Instabug's error reporting. + final Function(FlutterErrorDetails)? flutterErrorHandler; + + /// Custom handler for platform errors. + /// + /// This callback is called when a platform error occurs. It receives the + /// error object and stack trace. + /// + /// Example: + /// ```dart + /// InstabugWidget( + /// platformErrorHandler: (error, stack) { + /// print('Platform error: $error'); + /// // Custom error handling logic + /// }, + /// child: MyApp(), + /// ) + /// ``` + /// + /// Note: If this handler throws an error, it will be caught and logged + /// to prevent it from interfering with Instabug's error reporting. + final Function(Object, StackTrace)? platformErrorHandler; + + /// Whether to handle Flutter errors. + /// + /// If true, the Flutter error will be reported as a non-fatal crash, instead of a fatal crash. + final bool nonFatalFlutterErrors; + + /// The level of the non-fatal exception. + /// + /// This is used to determine the level of the non-fatal exception. + /// + /// Note: This has no effect if [nonFatalFlutterErrors] is false. + final NonFatalExceptionLevel nonFatalExceptionLevel; + + /// This widget is used to wrap the root of your application. It will automatically + /// configure both FlutterError.onError and PlatformDispatcher.instance.onError handlers to report errors to Instabug. + /// + /// Example: + /// ```dart + /// MaterialApp( + /// home: InstabugWidget( + /// child: MyApp(), + /// ), + /// ) + /// ``` + /// + /// Note: Custom error handlers are called before the error is reported to Instabug. + const InstabugWidget({ + Key? key, + required this.child, + this.flutterErrorHandler, + this.platformErrorHandler, + this.nonFatalFlutterErrors = false, + this.nonFatalExceptionLevel = NonFatalExceptionLevel.error, + }) : super(key: key); + + @override + State createState() => _InstabugWidgetState(); +} + +class _InstabugWidgetState extends State { + @override + void initState() { + super.initState(); + _setupErrorHandlers(); + } + + void _setupErrorHandlers() { + FlutterError.onError = (FlutterErrorDetails details) { + // Call user's custom handler if provided + if (widget.flutterErrorHandler != null) { + try { + widget.flutterErrorHandler!(details); + } catch (e) { + InstabugLogger.I.e( + 'Custom Flutter error handler failed: $e', + tag: 'InstabugWidget', + ); + } + } + + if (widget.nonFatalFlutterErrors) { + CrashReporting.reportHandledCrash( + details.exception, + details.stack ?? StackTrace.current, + level: widget.nonFatalExceptionLevel, + ); + } else { + CrashReporting.reportCrash( + details.exception, + details.stack ?? StackTrace.current, + ); + } + + FlutterError.presentError(details); + }; + + PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { + // Call user's custom handler if provided + if (widget.platformErrorHandler != null) { + try { + widget.platformErrorHandler!(error, stack); + } catch (e) { + InstabugLogger.I.e( + 'Custom platform error handler failed: $e', + tag: 'InstabugWidget', + ); + } + } + + CrashReporting.reportCrash(error, stack); + + return true; + }; + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +}