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

[firebase_crashlytics]Firebase Crashlytics plugin #1080

Merged
merged 38 commits into from
Mar 20, 2019

Conversation

kroikie
Copy link
Contributor

@kroikie kroikie commented Jan 15, 2019

Add plugin to FlutterFire that will report Dart side errors as non-fatal errors to the Firebase console.

Fixes: flutter/flutter#14765


### iOS Integration

Add the Crachlytics run scripts

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crashlytics

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks fixed.

@kroikie kroikie changed the title [WIP] Firebase Crashlytics plugin Firebase Crashlytics plugin Feb 14, 2019
Copy link
Contributor

@collinjackson collinjackson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this!

Setup `Crashlytics`:
```dart
void main() {
// Set reportInDevMode to true to see reports while in debug mode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put backticks around reportInDevMode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void main() {
// Set reportInDevMode to true to see reports while in debug mode
// This is only to be used for confirming that reports are being
// submitted as expected. It is not intended to be used in production.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change "It is not intended to be used in production" to "It is not intended to be used for everyday development."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

void main() {
// Set reportInDevMode to true to see reports while in debug mode
// This is only to be used for confirming that reports are being
// submitted as expected. It should not intended to be used in production.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback here, the "not intended to be used in production" comment confused me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void main() {
// Set reportInDevMode to true to see reports while in debug mode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backticks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

class Crashlytics {
static final Crashlytics instance = Crashlytics();

/// Set to true to have Errors sent to Crashlytics while in debug mode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't capitalize errors. I would clarify that you mean Flutter debug mode. You might also say that it defaults to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

static final Crashlytics instance = Crashlytics();

/// Set to true to have Errors sent to Crashlytics while in debug mode.
bool reportInDevMode = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would call it enableInDebugMode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

throw StateError('Error thrown by Crashlytics plugin');
}

Future<bool> isDebuggable() async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs dartdoc. Clarify that this means debuggability of the OEM parts of the app, not the Flutter app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used doc from Android, but will have to dig a bit deeper to get more info on what this means in both a Android and iOS context.

/// Logs to be included with report.
@visibleForTesting
final ListQueue<String> logs = ListQueue<String>(15);
int logSize = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is private and should have an underscore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/// Logs to be included with report.
@visibleForTesting
final ListQueue<String> logs = ListQueue<String>(15);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is private and should have an underscore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this has an underscore how will I use it in tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not part of the public API, it's an implementation detail and shouldn't be used in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@cyanglaz cyanglaz changed the title Firebase Crashlytics plugin [firebase_crashlytics]Firebase Crashlytics plugin Feb 23, 2019
@hpoul
Copy link
Contributor

hpoul commented Mar 4, 2019

I got a bit eager waiting for this to get merged, so I migrating a project from flutter_crashlytics to this plugin by including the git branch, and stumbled on a few things.. hope you don't mind a bit very early feedback (feel free to ignore):

  1. Imho there should be a way to cause a "real" crash, ie. calling [Crashlytics crash] because if I understand it correctly, crashlytics only ever sends crashes/logs/etc. when the app actually crashes and is subsequently restarted? (or even provide a isFatal flag in the onError method)
  2. It would be nice if logs are also sent periodically, because otherwise if a native part of my application crashes, all "context" logging from the dart side is lost (although i guess i could simply do this manually as a user of the plugin)
  3. api nitpicking .. I imagine user identifier, email, name would be usually set from the same location. how about providing one method with named parameters and only one native roundtrip :)

@danwulff
Copy link
Contributor

danwulff commented Mar 7, 2019

Consider updating FlutterFire.md while we're at it? https://github.com/flutter/plugins/blob/master/FlutterFire.md

}
}

void setBool(String key, bool value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setInt, setBool & setDouble are purely convenience methods on native SDKs (iOS/Android) and internally the SDK's convert these to a string value.

Perhaps it would be better to simplify this and have a singular method in Dart that accepts a bool, double, int or String value and this method converts to a String in dart and calls only one native method (setString)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Salakar thanks for the feedback. We are trying to keep the plugins interface as close to the native ones as possible. So we will stick with the separate methods for now.

setKey(key, value);
}

void setDouble(String key, double value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setInt, setBool & setDouble are purely convenience methods on native SDKs (iOS/Android) and internally the SDK's convert these to a string value.

Perhaps it would be better to simplify this and have a singular method in Dart that accepts a bool, double, int or String value and this method converts to a String in dart and calls only one native method (setString)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Salakar thanks for the feedback. We are trying to keep the plugins interface as close to the native ones as possible. So we will stick with the separate methods for now.

setKey(key, value);
}

void setInt(String key, int value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setInt, setBool & setDouble are purely convenience methods on native SDKs (iOS/Android) and internally the SDK's convert these to a string value.

Perhaps it would be better to simplify this and have a singular method in Dart that accepts a bool, double, int or String value and this method converts to a String in dart and calls only one native method (setString)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Salakar thanks for the feedback. We are trying to keep the plugins interface as close to the native ones as possible. So we will stick with the separate methods for now.

}

void crash() {
throw StateError('Error thrown by Crashlytics plugin');
Copy link
Member

@Salakar Salakar Mar 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not delegate to the native SDK methods to ensure a full fatal crash (app terminates) as Crashlytics only submits reports after a termination and subsequent app startup - wouldn't the Dart/Flutter error handler capture this in debug/dev and thus preventing this termination behaviour?

iOS: [[Crashlytics sharedInstance] crash];
Android: (new CrashTest()).crashAsyncTask(50); (crash off main thread to avoid being captured by error handlers in dev/debug)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Salakar This was considered, however most Flutter level crashes don't result in the underlying native application crashing, so throwing an error here was an attempt to represent the types of crashes likely to happen an a Flutter app and how the report will ultimately be submitted to Crashlytics.


dependencies {
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Crashlytics NDK support to capture low-level errors such as in Flutter Engine?

Dependency: implementation 'com.crashlytics.sdk.android:crashlytics-ndk:2.0.5'

Users build.gradle (documentation step only):

crashlytics {
  enableNdk true
}

See: https://docs.fabric.io/android/crashlytics/ndk.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that should be separate dependency

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Salakar thanks for the suggestion, we can definitely add this in a future version.

@kroikie
Copy link
Contributor Author

kroikie commented Mar 13, 2019

@hpoul thanks for the feedback:

  1. Non fatal crashes are the best we can do without changes to the underlying SDKs. We considered causing a fatal crash but decided that non fatals would be better till we have better native SDK support.

  2. Good point, thanks for the suggestion.

  3. It would be nice, but our current approach is to stay as close to the native SDK interfaces as possible. Still a good point and something we can consider later on.

@kroikie
Copy link
Contributor Author

kroikie commented Mar 14, 2019

Cirrus is all green, PR has stale CI status. See finished builds here:
https://cirrus-ci.com/build/5687774492491776

Make sure to call FirebaseApp.initializeApp(Context) first.
```

*Note:* When you are debugging on android, use a device or AVD with Google Play services.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: capitalize Android

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

void setKey(String key, dynamic value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still needs dartdocs


/// Logs to be included with report.
@visibleForTesting
final ListQueue<String> logs = ListQueue<String>(15);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not part of the public API, it's an implementation detail and shouldn't be used in tests.

// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
await channel.invokeMethod('Crashlytics#log', <String, dynamic>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a lot of unnecessary async waiting, especially if you have big logs. Can we send everything to the native side as an array argument to onError and log it there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed since logs are sent as part of error reporting.

// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
await channel.invokeMethod('Crashlytics#setInt', crashlyticsKey);
Copy link
Contributor

@collinjackson collinjackson Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a bit more concise to send over the params map and switch on the type of the data on the native side, so that you're defining fewer MethodCall invocations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@kroikie kroikie force-pushed the firebase_crashlytics branch from 0b1189b to e3f8209 Compare March 19, 2019 15:53
Copy link
Contributor

@collinjackson collinjackson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor style nit, lgtm otherwise

return result;
}

/// Returns the Kit Version.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be clearer to say Crashlytics SDK version instead of Kit version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

crashlytics.log('foo');
await crashlytics.onError(details);
expect(log[0].method, 'Crashlytics#onError');
expect(log[0].arguments['exception'], 'foo exception');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more readable and consistent with other tests to write this as a single expect comparing log with an array of maps.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like StackTrace.current might make it hard to use the isMethodCall matcher, in which case this is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After chat with @collinjackson we will leave this as is, since Stacktrace.current will likely not be the same on each execution.

@kroikie kroikie force-pushed the firebase_crashlytics branch from 4da42b1 to a2ec4a4 Compare March 20, 2019 16:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants