diff --git a/AUTHORS b/AUTHORS index 31a8f62280e0..855e3a4569f2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,4 +40,5 @@ Jonathan Younger Jose Sanchez Debkanchan Samadder Audrius Karosevicius -Lukasz Piliszczuk \ No newline at end of file +Lukasz Piliszczuk +SoundReply Solutions GmbH \ No newline at end of file diff --git a/packages/firebase_crashlytics/CHANGELOG.md b/packages/firebase_crashlytics/CHANGELOG.md index fb9c747d0023..17da29918f4e 100644 --- a/packages/firebase_crashlytics/CHANGELOG.md +++ b/packages/firebase_crashlytics/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.0 + +* **Breaking Change** Renamed `onError` to `reportFlutterError`. +* Added `recordError` method for errors caught using `runZoned`'s `onError`. + ## 0.0.4+12 * Update google-services Android gradle plugin to 4.3.0 in documentation and examples. diff --git a/packages/firebase_crashlytics/example/lib/main.dart b/packages/firebase_crashlytics/example/lib/main.dart index ba10fb26391b..81684ceaa272 100644 --- a/packages/firebase_crashlytics/example/lib/main.dart +++ b/packages/firebase_crashlytics/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; @@ -10,10 +12,11 @@ void main() { Crashlytics.instance.enableInDevMode = true; // Pass all uncaught errors to Crashlytics. - FlutterError.onError = (FlutterErrorDetails details) { - Crashlytics.instance.onError(details); - }; - runApp(MyApp()); + FlutterError.onError = Crashlytics.instance.recordFlutterError; + + runZoned>(() async { + runApp(MyApp()); + }, onError: Crashlytics.instance.recordError); } class MyApp extends StatefulWidget { @@ -61,6 +64,17 @@ class _MyAppState extends State { // Crashlytics. throw StateError('Uncaught error thrown by app.'); }), + FlatButton( + child: const Text('Async out of bounds'), + onPressed: () { + // Example of an exception that does not get caught + // by `FlutterError.onError` but is caught by the `onError` handler of + // `runZoned`. + Future.delayed(Duration(seconds: 2), () { + final List list = []; + print(list[100]); + }); + }), ], ), ), diff --git a/packages/firebase_crashlytics/example/test_driver/crashlytics.dart b/packages/firebase_crashlytics/example/test_driver/crashlytics.dart index c1a6c1d3df9e..40bb08717189 100644 --- a/packages/firebase_crashlytics/example/test_driver/crashlytics.dart +++ b/packages/firebase_crashlytics/example/test_driver/crashlytics.dart @@ -14,7 +14,7 @@ void main() { enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future); tearDownAll(() => allTestsCompleter.complete(null)); - test('onError', () async { + test('recordFlutterError', () async { // This is currently only testing that we can log errors without crashing. final Crashlytics crashlytics = Crashlytics.instance; await crashlytics.setUserName('testing'); @@ -24,7 +24,7 @@ void main() { crashlytics.setDouble('testDouble', 42.0); crashlytics.setString('testString', 'bar'); Crashlytics.instance.log('testing'); - await crashlytics.onError( + await crashlytics.recordFlutterError( FlutterErrorDetails( exception: 'testing', stack: StackTrace.fromString(''), diff --git a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart b/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart index aa5a67ef99e7..1409c50f8bfa 100644 --- a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart +++ b/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart @@ -24,35 +24,23 @@ class Crashlytics { static const MethodChannel channel = MethodChannel('plugins.flutter.io/firebase_crashlytics'); - /// Submits non-fatal crash report to Firebase Crashlytics. - Future onError(FlutterErrorDetails details) async { - print('Error caught by Crashlytics plugin:'); + /// Submits report of a non-fatal error caught by the Flutter framework. + /// to Firebase Crashlytics. + Future recordFlutterError(FlutterErrorDetails details) async { + print('Flutter error caught by Crashlytics plugin:'); - bool inDebugMode = false; - if (!enableInDevMode) { - assert(inDebugMode = true); - } + _recordError(details.exceptionAsString(), details.stack, + context: details.context); + } - if (inDebugMode && !enableInDevMode) { - print(Trace.format(details.stack)); - } else { - // Report error - final List stackTraceLines = - Trace.format(details.stack).trimRight().split('\n'); - final List> stackTraceElements = - getStackTraceElements(stackTraceLines); - await channel - .invokeMethod('Crashlytics#onError', { - 'exception': details.exceptionAsString(), - // FlutterErrorDetails.context has been migrated from a String to a - // DiagnosticsNode. Coerce it to a String here in a way that will work - // on both Strings and the new DiagnosticsNode values. See https://groups.google.com/forum/#!topic/flutter-announce/hp1RNIgej38 - 'context': '${details.context}', - 'stackTraceElements': stackTraceElements, - 'logs': _logs.toList(), - 'keys': _prepareKeys(), - }); - } + /// Submits a report of a non-fatal error. + /// + /// For errors generated by the Flutter framework, use [recordFlutterError] instead. + Future recordError(dynamic exception, StackTrace stack, + {dynamic context}) async { + print('Error caught by Crashlytics plugin :'); + + _recordError(exception, stack, context: context); } void crash() { @@ -196,4 +184,30 @@ class Crashlytics { } return elements; } + + Future _recordError(dynamic exception, StackTrace stack, + {dynamic context}) async { + bool inDebugMode = false; + if (!enableInDevMode) { + assert(inDebugMode = true); + } + + if (inDebugMode && !enableInDevMode) { + print(Trace.format(stack)); + } else { + // Report error + final List stackTraceLines = + Trace.format(stack).trimRight().split('\n'); + final List> stackTraceElements = + getStackTraceElements(stackTraceLines); + await channel + .invokeMethod('Crashlytics#onError', { + 'exception': "${exception.toString()}", + 'context': '$context', + 'stackTraceElements': stackTraceElements, + 'logs': _logs.toList(), + 'keys': _prepareKeys(), + }); + } + } } diff --git a/packages/firebase_crashlytics/pubspec.yaml b/packages/firebase_crashlytics/pubspec.yaml index 04b106c57540..d2d3d854c2a1 100644 --- a/packages/firebase_crashlytics/pubspec.yaml +++ b/packages/firebase_crashlytics/pubspec.yaml @@ -1,7 +1,8 @@ name: firebase_crashlytics -description: Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the +description: + Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the Firebase console. -version: 0.0.4+12 +version: 0.1.0 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_crashlytics diff --git a/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart b/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart index 22319891bcf1..5fef5850814d 100644 --- a/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart +++ b/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart @@ -33,7 +33,7 @@ void main() { log.clear(); }); - test('onError', () async { + test('recordFlutterError', () async { final FlutterErrorDetails details = FlutterErrorDetails( exception: 'foo exception', stack: StackTrace.current, @@ -46,7 +46,7 @@ void main() { crashlytics.setInt('testInt', 42); crashlytics.setDouble('testDouble', 42.0); crashlytics.setString('testString', 'bar'); - await crashlytics.onError(details); + await crashlytics.recordFlutterError(details); expect(log[0].method, 'Crashlytics#onError'); expect(log[0].arguments['exception'], 'foo exception'); expect(log[0].arguments['context'], 'foo context'); @@ -66,6 +66,30 @@ void main() { expect(log[0].arguments['keys'][3]['type'], 'string'); }); + test('recordError', () async { + crashlytics.enableInDevMode = true; + crashlytics.log('foo'); + await crashlytics.recordError('foo exception', StackTrace.current, + context: "context"); + expect(log[0].method, 'Crashlytics#onError'); + expect(log[0].arguments['exception'], 'foo exception'); + expect(log[0].arguments['context'], "context"); + expect(log[0].arguments['logs'], isNotEmpty); + expect(log[0].arguments['logs'], contains('foo')); + expect(log[0].arguments['keys'][0]['key'], 'testBool'); + expect(log[0].arguments['keys'][0]['value'], isTrue); + expect(log[0].arguments['keys'][0]['type'], 'boolean'); + expect(log[0].arguments['keys'][1]['key'], 'testInt'); + expect(log[0].arguments['keys'][1]['value'], 42); + expect(log[0].arguments['keys'][1]['type'], 'int'); + expect(log[0].arguments['keys'][2]['key'], 'testDouble'); + expect(log[0].arguments['keys'][2]['value'], 42.0); + expect(log[0].arguments['keys'][2]['type'], 'double'); + expect(log[0].arguments['keys'][3]['key'], 'testString'); + expect(log[0].arguments['keys'][3]['value'], 'bar'); + expect(log[0].arguments['keys'][3]['type'], 'string'); + }); + test('isDebuggable', () async { expect(await crashlytics.isDebuggable(), true); expect(