Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[firebase_crashlytics] Migrate to new Crashlytics SDK #2288

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
29b6498
[crashlytics] Migrate to new Crashlytics SDK
axel-op Apr 2, 2020
3cafc46
[crashlytics] Migration to new SDK: update Obj-C files
axel-op Apr 8, 2020
9346447
Update generateFrame method
axel-op Apr 14, 2020
bc7c7a7
Remove getVersion and isDebuggable
axel-op Apr 14, 2020
d03e47b
Wrong call to argument
axel-op Apr 14, 2020
e359b09
Missing semicolons
axel-op Apr 14, 2020
28891ae
Use recordExceptionModel instead of recordError
axel-op Apr 14, 2020
ec3cc0f
Update project.pbxproj
axel-op Apr 14, 2020
7685fe0
Don't call [FIRApp configure] twice
axel-op Apr 15, 2020
7cf3512
Format
axel-op Apr 15, 2020
0628c26
Update Gradle dependency
axel-op Apr 19, 2020
6386231
Update README
axel-op Apr 19, 2020
c117ec0
Update README.md
axel-op Apr 20, 2020
5a46b56
Add documentation
axel-op Apr 22, 2020
03facb5
Update CHANGELOG.md
axel-op Apr 24, 2020
146ba8f
Edit Gradle plugin version
axel-op Apr 27, 2020
3c571ed
Update README.md
axel-op May 7, 2020
de71916
Replace `runZoned` by `runZonedGuarded` and update SDK constraints
axel-op May 8, 2020
fa6501d
Update Flutter SDK constraints
axel-op May 8, 2020
0cfa894
Update CHANGELOG.md
axel-op May 13, 2020
d69d918
Update UIApplicationDelegate for example iOs app
axel-op May 18, 2020
4fc4835
Fix bug about key extraction on iOs
axel-op May 20, 2020
bda47bc
Implement [setCustomKey] on Dart side
axel-op May 21, 2020
497e3f9
Update packages/firebase_crashlytics/CHANGELOG.md
axel-op May 21, 2020
1adeca9
Check that [value] passed to [setCustomKey] is not null
axel-op May 21, 2020
324dcf6
Update CHANGELOG
axel-op Jun 1, 2020
1ae9cab
Merge remote-tracking branch 'upstream/master' into crashlytics-new-sdk
axel-op Jul 4, 2020
0c883fa
Send logs and keys are they are set
axel-op Jul 4, 2020
66d2d14
Update tests
axel-op Jul 4, 2020
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
13 changes: 13 additions & 0 deletions packages/firebase_crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Changelog

## 0.2.0

* **Breaking**: Migration to the new Firebase Crashlytics SDK. Follow the [migration guide](https://firebase.google.com/docs/crashlytics/upgrade-sdk?platform=android) (steps 1 and 2) to update your app.
axel-op marked this conversation as resolved.
Show resolved Hide resolved
* **Breaking**: the following methods have been removed:
* `setUserEmail`
* `setUserName`
* `getVersion`
* `isDebuggable`
* **Breaking**: the methods `setInt`, `setDouble`, `setString` and `setBool` have been replaced by `setCustomKey`.
* Fixes a bug that prevented keys from being set on iOS devices.

## 0.1.3+3

* Fix for missing UserAgent.h compilation failures.
Expand Down
93 changes: 64 additions & 29 deletions packages/firebase_crashlytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,72 @@ For Flutter plugins for other Firebase products, see [README.md](https://github.

To use the `firebase_crashlytics` plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/firebase_crashlytics#pub-pkg-tab-installing).

The following instructions are from [the official installation page](https://firebase.google.com/docs/crashlytics/get-started-new-sdk).

### Android integration

Enable the Google services by configuring the Gradle scripts as such:

1. Add the Fabric repository to the `[project]/android/build.gradle` file.
```
repositories {
google()
jcenter()
// Additional repository for fabric resources
maven {
url 'https://maven.fabric.io/public'
1. Check that you have Google's Maven repository in your **project-level** `build.gradle` file (`[project]/android/build.gradle`).

```gradle
buildscript {
repositories {
// Add this
google()

// ... you may have other repositories
}
}
allprojects {
repositories {
// and this
google()

// ...
}
}
```

2. Add the following classpaths to the `[project]/android/build.gradle` file.
2. Add the following classpaths to your **project-level** `build.gradle` file (`[project]/android/build.gradle`).

```gradle
dependencies {
// Example existing classpath
classpath 'com.android.tools.build:gradle:3.2.1'
// Add the google services classpath
classpath 'com.google.gms:google-services:4.3.0'
// Add fabric classpath
classpath 'io.fabric.tools:gradle:1.26.1'
buildscript {
dependencies {
// Check that you have the Google Services Gradle plugin v4.3.2 or later (if not, add it).
classpath 'com.google.gms:google-services:4.3.3'

// Add the Crashlytics Gradle plugin.
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0'

// ... you may have other classpaths
}
}
```

2. Apply the following plugins in the `[project]/android/app/build.gradle` file.
3. Apply the following plugins in your **app-level** `build.gradle` file (`[project]/android/app/build.gradle`).

```gradle
// ADD THIS AT THE BOTTOM
apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
```

*Note:* If this section is not completed, you will get an error like this:
4. Add the SDK dependencies in your **app-level** `build.gradle` file (`[project]/android/app/build.gradle`).

```gradle
dependencies {
// Optional but recommended: Add the Firebase SDK for Google Analytics.
implementation 'com.google.firebase:firebase-analytics:17.4.0'

// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.0.0'
Copy link
Contributor

@jpelgrim jpelgrim Jun 17, 2020

Choose a reason for hiding this comment

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

This dependency is already in the firebase_crashlytics package ./android/build.gradle file, right? We don't need to add it to our Flutter app build.gradle file IMO.

Update: Works fine without it in my case. I don't think the whole fourth step is needed. It's even a bit confusing and misleading to add the firebase-analytics dependency too here since it's not necessary for Firebase Crashlytics to work.

Copy link
Contributor

@MaikuB MaikuB Jun 18, 2020

Choose a reason for hiding this comment

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

Yeah if it's already a dependency used by the plugin then developers shouldn't need to add it to their app's build.gradle file

cc @axel-op

Choose a reason for hiding this comment

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

Ditto for the firebase_analytics ! I don't like the way the official documentation like this: https://firebase.google.com/docs/flutter/setup?platform=ios

Encourages other packages. It just leads to possible reader confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will be updated when the package will depend on the new versions of firebase_core.

}
```

*Note:* If this section is not completed, you will get an error like this:

```console
java.lang.IllegalStateException:
Default FirebaseApp is not initialized in this process [package name].
Make sure to call FirebaseApp.initializeApp(Context) first.
Expand All @@ -64,18 +93,20 @@ Add the Crashlytics run scripts:
1. From Xcode select `Runner` from the project navigation.
1. Select the `Build Phases` tab.
1. Click `+ Add a new build phase`, and select `New Run Script Phase`.
1. Add `${PODS_ROOT}/Fabric/run` to the `Type a script...` text box.
1. Add `${PODS_ROOT}/FirebaseCrashlytics/run` to the `Type a script...` text box.
1. If you are using Xcode 10, add the location of `Info.plist`, built by your app, to the `Build Phase's Input Files` field.
E.g.: `$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)`

### Use the plugin

Add the following imports to your Dart code:

```dart
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
```

Setup `Crashlytics`:

```dart
void main() {
// Set `enableInDevMode` to true to see reports while in debug mode
Expand All @@ -91,22 +122,26 @@ void main() {
}
```

Overriding `FlutterError.onError` with `Crashlytics.instance.recordFlutterError` will automatically catch all
errors that are thrown from within the Flutter framework.
If you want to catch errors that occur in `runZoned`,
you can supply `Crashlytics.instance.recordError` to the `onError` parameter:
Overriding `FlutterError.onError` with `Crashlytics.instance.recordFlutterError` will automatically catch all errors that are thrown from within the Flutter framework.

If you want to catch errors that occur in [`runZonedGuarded`](https://api.dart.dev/stable/dart-async/runZonedGuarded.html), you can supply `Crashlytics.instance.recordError` to the `onError` positioned parameter:

```dart
runZoned<Future<void>>(() async {
runZonedGuarded<Future<void>>(
() async {
// ...
}, onError: Crashlytics.instance.recordError);
```
},
Crashlytics.instance.recordError,
);
```

## Result

If an error is caught, you should see the following messages in your logs:
```

```console
flutter: Flutter error caught by Crashlytics plugin:
// OR if you use recordError for runZoned:
// OR if you use recordError for runZonedGuarded:
flutter: Error caught by Crashlytics plugin <recordError>:
// Exception, context, information, and stack trace in debug mode
// OR if not in debug mode:
Expand Down
5 changes: 1 addition & 4 deletions packages/firebase_crashlytics/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ rootProject.allprojects {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
}

Expand All @@ -37,7 +34,7 @@ android {
}

dependencies {
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
implementation 'com.google.firebase:firebase-crashlytics:17.0.0'
implementation 'com.google.firebase:firebase-common:16.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

import android.content.Context;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import io.fabric.sdk.android.Fabric;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
Expand Down Expand Up @@ -42,11 +41,6 @@ private static MethodChannel setup(BinaryMessenger binaryMessenger, Context cont
final MethodChannel channel =
new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_crashlytics");
channel.setMethodCallHandler(new FirebaseCrashlyticsPlugin());

if (!Fabric.isInitialized()) {
Fabric.with(context, new Crashlytics());
}

return channel;
}

Expand All @@ -57,69 +51,47 @@ public static void registerWith(Registrar registrar) {

@Override
public void onMethodCall(MethodCall call, Result result) {
final FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
if (call.method.equals("Crashlytics#onError")) {
// Add logs.
List<String> logs = call.argument("logs");
for (String log : logs) {
Crashlytics.log(log);
final List<String> logs = call.argument("logs");
for (String l : logs) {
crashlytics.log(l);
}

// Set keys.
List<Map<String, Object>> keys = call.argument("keys");
for (Map<String, Object> key : keys) {
switch ((String) key.get("type")) {
case "int":
Crashlytics.setInt((String) key.get("key"), (int) key.get("value"));
break;
case "double":
Crashlytics.setDouble((String) key.get("key"), (double) key.get("value"));
break;
case "string":
Crashlytics.setString((String) key.get("key"), (String) key.get("value"));
break;
case "boolean":
Crashlytics.setBool((String) key.get("key"), (boolean) key.get("value"));
break;
}
final Map<String, Object> keys = call.argument("keys");
for (String key : keys.keySet()) {
crashlytics.setCustomKey(key, (String) keys.get(key));
}

// Report crash.
String dartExceptionMessage = (String) call.argument("exception");
Exception exception = new Exception(dartExceptionMessage);
List<Map<String, String>> errorElements = call.argument("stackTraceElements");
List<StackTraceElement> elements = new ArrayList<>();
final String dartExceptionMessage = (String) call.argument("exception");
final Exception exception = new Exception(dartExceptionMessage);
final List<Map<String, String>> errorElements = call.argument("stackTraceElements");
final List<StackTraceElement> elements = new ArrayList<>();
for (Map<String, String> errorElement : errorElements) {
StackTraceElement stackTraceElement = generateStackTraceElement(errorElement);
final StackTraceElement stackTraceElement = generateStackTraceElement(errorElement);
if (stackTraceElement != null) {
elements.add(stackTraceElement);
}
}
exception.setStackTrace(elements.toArray(new StackTraceElement[elements.size()]));

Crashlytics.setString("exception", (String) call.argument("exception"));
crashlytics.setCustomKey("exception", (String) call.argument("exception"));

// Set a "reason" (to match iOS) to show where the exception was thrown.
final String context = call.argument("context");
if (context != null) Crashlytics.setString("reason", "thrown " + context);
if (context != null) crashlytics.setCustomKey("reason", "thrown " + context);

// Log information.
final String information = call.argument("information");
if (information != null && !information.isEmpty()) Crashlytics.log(information);
if (information != null && !information.isEmpty()) crashlytics.log(information);

Crashlytics.logException(exception);
crashlytics.recordException(exception);
result.success("Error reported to Crashlytics.");
} else if (call.method.equals("Crashlytics#isDebuggable")) {
result.success(Fabric.isDebuggable());
} else if (call.method.equals("Crashlytics#getVersion")) {
result.success(Crashlytics.getInstance().getVersion());
} else if (call.method.equals("Crashlytics#setUserEmail")) {
Crashlytics.setUserEmail((String) call.argument("email"));
result.success(null);
} else if (call.method.equals("Crashlytics#setUserIdentifier")) {
Crashlytics.setUserIdentifier((String) call.argument("identifier"));
result.success(null);
} else if (call.method.equals("Crashlytics#setUserName")) {
Crashlytics.setUserName((String) call.argument("name"));
crashlytics.setUserId((String) call.argument("identifier"));
result.success(null);
} else {
result.notImplemented();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FirebaseCrashlyticsPlugin *instance = [[FirebaseCrashlyticsPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];

[Fabric with:@[ [Crashlytics self] ]];

SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:");
if ([FIRApp respondsToSelector:sel]) {
[FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION];
Expand All @@ -41,37 +39,20 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
// Add logs.
NSArray *logs = call.arguments[@"logs"];
for (NSString *log in logs) {
// Here and below, use CLSLog instead of CLS_LOG to try and avoid
// automatic inclusion of the current code location. It also ensures that
// the log is only written to Crashlytics and not also to the offline log
// as explained here:
// https://support.crashlytics.com/knowledgebase/articles/92519-how-do-i-use-logging
CLSLog(@"%@", log);
axel-op marked this conversation as resolved.
Show resolved Hide resolved
[[FIRCrashlytics crashlytics] logWithFormat:@"%@", log];
}

// Set keys.
NSArray *keys = call.arguments[@"keys"];
for (NSDictionary *key in keys) {
if ([@"int" isEqualToString:key[@"type"]]) {
[[Crashlytics sharedInstance] setIntValue:(int)call.arguments[@"value"]
forKey:call.arguments[@"key"]];
} else if ([@"double" isEqualToString:key[@"type"]]) {
[[Crashlytics sharedInstance] setFloatValue:[call.arguments[@"value"] floatValue]
forKey:call.arguments[@"key"]];
} else if ([@"string" isEqualToString:key[@"type"]]) {
[[Crashlytics sharedInstance] setObjectValue:call.arguments[@"value"]
forKey:call.arguments[@"key"]];
} else if ([@"boolean" isEqualToString:key[@"type"]]) {
[[Crashlytics sharedInstance] setBoolValue:[call.arguments[@"value"] boolValue]
forKey:call.arguments[@"key"]];
}
NSDictionary *keys = call.arguments[@"keys"];
for (NSString *key in keys) {
[[FIRCrashlytics crashlytics] setCustomValue:keys[key] forKey:key];
}

// Add additional information from the Flutter framework to the exception reported in
// Crashlytics.
NSString *information = call.arguments[@"information"];
if ([information length] != 0) {
CLSLog(@"%@", information);
[[FIRCrashlytics crashlytics] logWithFormat:@"%@", information];
}

// Report crash.
Expand All @@ -87,36 +68,26 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
reason = [NSString stringWithFormat:@"thrown %@", context];
}

[[Crashlytics sharedInstance] recordCustomExceptionName:call.arguments[@"exception"]
reason:reason
frameArray:frames];
FIRExceptionModel *exception =
[FIRExceptionModel exceptionModelWithName:call.arguments[@"exception"] reason:reason];

exception.stackTrace = frames;

[[FIRCrashlytics crashlytics] recordExceptionModel:exception];
result(@"Error reported to Crashlytics.");
} else if ([@"Crashlytics#isDebuggable" isEqualToString:call.method]) {
result([NSNumber numberWithBool:[Crashlytics sharedInstance].debugMode]);
} else if ([@"Crashlytics#getVersion" isEqualToString:call.method]) {
result([Crashlytics sharedInstance].version);
} else if ([@"Crashlytics#setUserEmail" isEqualToString:call.method]) {
[[Crashlytics sharedInstance] setUserEmail:call.arguments[@"email"]];
result(nil);
} else if ([@"Crashlytics#setUserName" isEqualToString:call.method]) {
[[Crashlytics sharedInstance] setUserName:call.arguments[@"name"]];
result(nil);
} else if ([@"Crashlytics#setUserIdentifier" isEqualToString:call.method]) {
[[Crashlytics sharedInstance] setUserIdentifier:call.arguments[@"identifier"]];
[[FIRCrashlytics crashlytics] setUserID:call.arguments[@"identifier"]];
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
}

- (CLSStackFrame *)generateFrame:(NSDictionary *)errorElement {
CLSStackFrame *frame = [CLSStackFrame stackFrame];

frame.library = [errorElement valueForKey:@"class"];
frame.symbol = [errorElement valueForKey:@"method"];
frame.fileName = [errorElement valueForKey:@"file"];
frame.lineNumber = [[errorElement valueForKey:@"line"] intValue];

- (FIRStackFrame *)generateFrame:(NSDictionary *)errorElement {
FIRStackFrame *frame =
[FIRStackFrame stackFrameWithSymbol:[errorElement valueForKey:@"method"]
file:[errorElement valueForKey:@"file"]
line:[[errorElement valueForKey:@"line"] intValue]];
return frame;
}

Expand Down
Loading