Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 589f4e1

Browse files
hffmnncollinjackson
andcommitted
[firebase_crashlytics] runtime exceptions (#1803)
* update AUTHORS file * add new method recordError * rename onError to recordFlutterError Co-Authored-By: Collin Jackson <jackson@google.com>
1 parent fa7961e commit 589f4e1

7 files changed

Lines changed: 97 additions & 38 deletions

File tree

AUTHORS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ Jonathan Younger <jonathan@daikini.com>
4040
Jose Sanchez <josesm82@gmail.com>
4141
Debkanchan Samadder <debu.samadder@gmail.com>
4242
Audrius Karosevicius <audrius.karosevicius@gmail.com>
43-
Lukasz Piliszczuk <lukasz@intheloup.io>
43+
Lukasz Piliszczuk <lukasz@intheloup.io>
44+
SoundReply Solutions GmbH <ch@soundreply.com>

packages/firebase_crashlytics/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.1.0
2+
3+
* **Breaking Change** Renamed `onError` to `reportFlutterError`.
4+
* Added `recordError` method for errors caught using `runZoned`'s `onError`.
5+
16
## 0.0.4+12
27

38
* Update google-services Android gradle plugin to 4.3.0 in documentation and examples.

packages/firebase_crashlytics/example/lib/main.dart

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/material.dart';
24

35
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
@@ -10,10 +12,11 @@ void main() {
1012
Crashlytics.instance.enableInDevMode = true;
1113

1214
// Pass all uncaught errors to Crashlytics.
13-
FlutterError.onError = (FlutterErrorDetails details) {
14-
Crashlytics.instance.onError(details);
15-
};
16-
runApp(MyApp());
15+
FlutterError.onError = Crashlytics.instance.recordFlutterError;
16+
17+
runZoned<Future<void>>(() async {
18+
runApp(MyApp());
19+
}, onError: Crashlytics.instance.recordError);
1720
}
1821

1922
class MyApp extends StatefulWidget {
@@ -61,6 +64,17 @@ class _MyAppState extends State<MyApp> {
6164
// Crashlytics.
6265
throw StateError('Uncaught error thrown by app.');
6366
}),
67+
FlatButton(
68+
child: const Text('Async out of bounds'),
69+
onPressed: () {
70+
// Example of an exception that does not get caught
71+
// by `FlutterError.onError` but is caught by the `onError` handler of
72+
// `runZoned`.
73+
Future<void>.delayed(Duration(seconds: 2), () {
74+
final List<int> list = <int>[];
75+
print(list[100]);
76+
});
77+
}),
6478
],
6579
),
6680
),

packages/firebase_crashlytics/example/test_driver/crashlytics.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ void main() {
1414
enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future);
1515
tearDownAll(() => allTestsCompleter.complete(null));
1616

17-
test('onError', () async {
17+
test('recordFlutterError', () async {
1818
// This is currently only testing that we can log errors without crashing.
1919
final Crashlytics crashlytics = Crashlytics.instance;
2020
await crashlytics.setUserName('testing');
@@ -24,7 +24,7 @@ void main() {
2424
crashlytics.setDouble('testDouble', 42.0);
2525
crashlytics.setString('testString', 'bar');
2626
Crashlytics.instance.log('testing');
27-
await crashlytics.onError(
27+
await crashlytics.recordFlutterError(
2828
FlutterErrorDetails(
2929
exception: 'testing',
3030
stack: StackTrace.fromString(''),

packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,23 @@ class Crashlytics {
2424
static const MethodChannel channel =
2525
MethodChannel('plugins.flutter.io/firebase_crashlytics');
2626

27-
/// Submits non-fatal crash report to Firebase Crashlytics.
28-
Future<void> onError(FlutterErrorDetails details) async {
29-
print('Error caught by Crashlytics plugin:');
27+
/// Submits report of a non-fatal error caught by the Flutter framework.
28+
/// to Firebase Crashlytics.
29+
Future<void> recordFlutterError(FlutterErrorDetails details) async {
30+
print('Flutter error caught by Crashlytics plugin:');
3031

31-
bool inDebugMode = false;
32-
if (!enableInDevMode) {
33-
assert(inDebugMode = true);
34-
}
32+
_recordError(details.exceptionAsString(), details.stack,
33+
context: details.context);
34+
}
3535

36-
if (inDebugMode && !enableInDevMode) {
37-
print(Trace.format(details.stack));
38-
} else {
39-
// Report error
40-
final List<String> stackTraceLines =
41-
Trace.format(details.stack).trimRight().split('\n');
42-
final List<Map<String, String>> stackTraceElements =
43-
getStackTraceElements(stackTraceLines);
44-
await channel
45-
.invokeMethod<dynamic>('Crashlytics#onError', <String, dynamic>{
46-
'exception': details.exceptionAsString(),
47-
// FlutterErrorDetails.context has been migrated from a String to a
48-
// DiagnosticsNode. Coerce it to a String here in a way that will work
49-
// on both Strings and the new DiagnosticsNode values. See https://groups.google.com/forum/#!topic/flutter-announce/hp1RNIgej38
50-
'context': '${details.context}',
51-
'stackTraceElements': stackTraceElements,
52-
'logs': _logs.toList(),
53-
'keys': _prepareKeys(),
54-
});
55-
}
36+
/// Submits a report of a non-fatal error.
37+
///
38+
/// For errors generated by the Flutter framework, use [recordFlutterError] instead.
39+
Future<void> recordError(dynamic exception, StackTrace stack,
40+
{dynamic context}) async {
41+
print('Error caught by Crashlytics plugin <recordError>:');
42+
43+
_recordError(exception, stack, context: context);
5644
}
5745

5846
void crash() {
@@ -196,4 +184,30 @@ class Crashlytics {
196184
}
197185
return elements;
198186
}
187+
188+
Future<void> _recordError(dynamic exception, StackTrace stack,
189+
{dynamic context}) async {
190+
bool inDebugMode = false;
191+
if (!enableInDevMode) {
192+
assert(inDebugMode = true);
193+
}
194+
195+
if (inDebugMode && !enableInDevMode) {
196+
print(Trace.format(stack));
197+
} else {
198+
// Report error
199+
final List<String> stackTraceLines =
200+
Trace.format(stack).trimRight().split('\n');
201+
final List<Map<String, String>> stackTraceElements =
202+
getStackTraceElements(stackTraceLines);
203+
await channel
204+
.invokeMethod<dynamic>('Crashlytics#onError', <String, dynamic>{
205+
'exception': "${exception.toString()}",
206+
'context': '$context',
207+
'stackTraceElements': stackTraceElements,
208+
'logs': _logs.toList(),
209+
'keys': _prepareKeys(),
210+
});
211+
}
212+
}
199213
}

packages/firebase_crashlytics/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
name: firebase_crashlytics
2-
description: Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the
2+
description:
3+
Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the
34
Firebase console.
4-
version: 0.0.4+12
5+
version: 0.1.0
56
author: Flutter Team <flutter-dev@google.com>
67
homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_crashlytics
78

packages/firebase_crashlytics/test/firebase_crashlytics_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void main() {
3333
log.clear();
3434
});
3535

36-
test('onError', () async {
36+
test('recordFlutterError', () async {
3737
final FlutterErrorDetails details = FlutterErrorDetails(
3838
exception: 'foo exception',
3939
stack: StackTrace.current,
@@ -46,7 +46,7 @@ void main() {
4646
crashlytics.setInt('testInt', 42);
4747
crashlytics.setDouble('testDouble', 42.0);
4848
crashlytics.setString('testString', 'bar');
49-
await crashlytics.onError(details);
49+
await crashlytics.recordFlutterError(details);
5050
expect(log[0].method, 'Crashlytics#onError');
5151
expect(log[0].arguments['exception'], 'foo exception');
5252
expect(log[0].arguments['context'], 'foo context');
@@ -66,6 +66,30 @@ void main() {
6666
expect(log[0].arguments['keys'][3]['type'], 'string');
6767
});
6868

69+
test('recordError', () async {
70+
crashlytics.enableInDevMode = true;
71+
crashlytics.log('foo');
72+
await crashlytics.recordError('foo exception', StackTrace.current,
73+
context: "context");
74+
expect(log[0].method, 'Crashlytics#onError');
75+
expect(log[0].arguments['exception'], 'foo exception');
76+
expect(log[0].arguments['context'], "context");
77+
expect(log[0].arguments['logs'], isNotEmpty);
78+
expect(log[0].arguments['logs'], contains('foo'));
79+
expect(log[0].arguments['keys'][0]['key'], 'testBool');
80+
expect(log[0].arguments['keys'][0]['value'], isTrue);
81+
expect(log[0].arguments['keys'][0]['type'], 'boolean');
82+
expect(log[0].arguments['keys'][1]['key'], 'testInt');
83+
expect(log[0].arguments['keys'][1]['value'], 42);
84+
expect(log[0].arguments['keys'][1]['type'], 'int');
85+
expect(log[0].arguments['keys'][2]['key'], 'testDouble');
86+
expect(log[0].arguments['keys'][2]['value'], 42.0);
87+
expect(log[0].arguments['keys'][2]['type'], 'double');
88+
expect(log[0].arguments['keys'][3]['key'], 'testString');
89+
expect(log[0].arguments['keys'][3]['value'], 'bar');
90+
expect(log[0].arguments['keys'][3]['type'], 'string');
91+
});
92+
6993
test('isDebuggable', () async {
7094
expect(await crashlytics.isDebuggable(), true);
7195
expect(

0 commit comments

Comments
 (0)