Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ Jonathan Younger <[email protected]>
Jose Sanchez <[email protected]>
Debkanchan Samadder <[email protected]>
Audrius Karosevicius <[email protected]>
Lukasz Piliszczuk <[email protected]>
Lukasz Piliszczuk <[email protected]>
SoundReply Solutions GmbH <[email protected]>
5 changes: 5 additions & 0 deletions packages/firebase_crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
22 changes: 18 additions & 4 deletions packages/firebase_crashlytics/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/material.dart';

import 'package:firebase_crashlytics/firebase_crashlytics.dart';
Expand All @@ -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<Future<void>>(() async {
runApp(MyApp());
}, onError: Crashlytics.instance.recordError);
}

class MyApp extends StatefulWidget {
Expand Down Expand Up @@ -61,6 +64,17 @@ class _MyAppState extends State<MyApp> {
// 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<void>.delayed(Duration(seconds: 2), () {
final List<int> list = <int>[];
print(list[100]);
});
}),
],
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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(''),
Expand Down
68 changes: 41 additions & 27 deletions packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> 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<void> 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<String> stackTraceLines =
Trace.format(details.stack).trimRight().split('\n');
final List<Map<String, String>> stackTraceElements =
getStackTraceElements(stackTraceLines);
await channel
.invokeMethod<dynamic>('Crashlytics#onError', <String, dynamic>{
'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<void> recordError(dynamic exception, StackTrace stack,
{dynamic context}) async {
print('Error caught by Crashlytics plugin <recordError>:');

_recordError(exception, stack, context: context);
}

void crash() {
Expand Down Expand Up @@ -196,4 +184,30 @@ class Crashlytics {
}
return elements;
}

Future<void> _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<String> stackTraceLines =
Trace.format(stack).trimRight().split('\n');
final List<Map<String, String>> stackTraceElements =
getStackTraceElements(stackTraceLines);
await channel
.invokeMethod<dynamic>('Crashlytics#onError', <String, dynamic>{
'exception': "${exception.toString()}",
'context': '$context',
'stackTraceElements': stackTraceElements,
'logs': _logs.toList(),
'keys': _prepareKeys(),
});
}
}
}
5 changes: 3 additions & 2 deletions packages/firebase_crashlytics/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_crashlytics

Expand Down
28 changes: 26 additions & 2 deletions packages/firebase_crashlytics/test/firebase_crashlytics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void main() {
log.clear();
});

test('onError', () async {
test('recordFlutterError', () async {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: 'foo exception',
stack: StackTrace.current,
Expand All @@ -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');
Expand All @@ -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(
Expand Down