diff --git a/packages/firebase_crashlytics/CHANGELOG.md b/packages/firebase_crashlytics/CHANGELOG.md index b3035bb55fb9..718a52735171 100644 --- a/packages/firebase_crashlytics/CHANGELOG.md +++ b/packages/firebase_crashlytics/CHANGELOG.md @@ -1,3 +1,17 @@ +# Changelog + +## 0.2.0 + +* **Breaking change**: 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. +* **Breaking change**: the following methods have been removed: + * `setUserEmail` + * `setUserName` + * `getVersion` + * `isDebuggable` +* **Breaking change**: the methods `setInt`, `setDouble`, `setString` and `setBool` have been replaced by `setCustomKey`. +* Logs and keys are sent to Crashlytics as they're set. +* Fixes a bug that prevented keys from being set on iOS devices. + ## 0.1.4+1 * Put current stack trace into report if no other stack trace is supplied. diff --git a/packages/firebase_crashlytics/README.md b/packages/firebase_crashlytics/README.md index 504ba59f5cec..2c57da97843e 100644 --- a/packages/firebase_crashlytics/README.md +++ b/packages/firebase_crashlytics/README.md @@ -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' +} ``` + +*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. @@ -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 @@ -91,13 +122,17 @@ 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>(() async { +runZonedGuarded>( + () async { // ... + }, + Crashlytics.instance.recordError, +); }, onError: Crashlytics.instance.recordError); ``` @@ -117,9 +152,10 @@ Isolate.current.addErrorListener(RawReceivePort((pair) async { ## 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 : // Exception, context, information, and stack trace in debug mode // OR if not in debug mode: diff --git a/packages/firebase_crashlytics/android/build.gradle b/packages/firebase_crashlytics/android/build.gradle index a557dc49e8a2..2c00e17cbd33 100644 --- a/packages/firebase_crashlytics/android/build.gradle +++ b/packages/firebase_crashlytics/android/build.gradle @@ -16,9 +16,6 @@ rootProject.allprojects { repositories { google() jcenter() - maven { - url 'https://maven.fabric.io/public' - } } } @@ -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' } diff --git a/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FirebaseCrashlyticsPlugin.java b/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FirebaseCrashlyticsPlugin.java index 6de881d1da50..3bc4fa763aa1 100644 --- a/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FirebaseCrashlyticsPlugin.java +++ b/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FirebaseCrashlyticsPlugin.java @@ -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; @@ -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; } @@ -57,69 +51,44 @@ 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 logs = call.argument("logs"); - for (String log : logs) { - Crashlytics.log(log); - } - - // Set keys. - List> keys = call.argument("keys"); - for (Map 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; - } - } - // Report crash. - String dartExceptionMessage = (String) call.argument("exception"); - Exception exception = new Exception(dartExceptionMessage); - List> errorElements = call.argument("stackTraceElements"); - List elements = new ArrayList<>(); + final String dartExceptionMessage = (String) call.argument("exception"); + final Exception exception = new Exception(dartExceptionMessage); + final List> errorElements = call.argument("stackTraceElements"); + final List elements = new ArrayList<>(); for (Map 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")); + crashlytics.setUserId((String) call.argument("identifier")); + result.success(null); + } else if (call.method.equals("Crashlytics#setKey")) { + final String key = (String) call.argument("key"); + final String value = (String) call.argument("value"); + crashlytics.setCustomKey(key, value); result.success(null); - } else if (call.method.equals("Crashlytics#setUserName")) { - Crashlytics.setUserName((String) call.argument("name")); + } else if (call.method.equals("Crashlytics#log")) { + final String msg = (String) call.argument("log"); + crashlytics.log(msg); result.success(null); } else { result.notImplemented(); diff --git a/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m index 607f4def8f02..0fb2b6ede8c0 100644 --- a/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m +++ b/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m @@ -18,8 +18,6 @@ + (void)registerWithRegistrar:(NSObject *)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]; @@ -38,40 +36,11 @@ - (instancetype)init { - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"Crashlytics#onError" isEqualToString:call.method]) { - // 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); - } - - // 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"]]; - } - } - // 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. @@ -87,36 +56,35 @@ - (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"]]; + } else if ([@"Crashlytics#setUserIdentifier" isEqualToString:call.method]) { + [[FIRCrashlytics crashlytics] setUserID:call.arguments[@"identifier"]]; result(nil); - } else if ([@"Crashlytics#setUserName" isEqualToString:call.method]) { - [[Crashlytics sharedInstance] setUserName:call.arguments[@"name"]]; + } else if ([@"Crashlytics#setKey" isEqualToString:call.method]) { + NSString *key = call.arguments[@"key"]; + NSString *value = call.arguments[@"value"]; + [[FIRCrashlytics crashlytics] setCustomValue:value forKey:key]; result(nil); - } else if ([@"Crashlytics#setUserIdentifier" isEqualToString:call.method]) { - [[Crashlytics sharedInstance] setUserIdentifier:call.arguments[@"identifier"]]; + } else if ([@"Crashlytics#log" isEqualToString:call.method]) { + NSString *msg = call.arguments[@"log"]; + [[FIRCrashlytics crashlytics] logWithFormat:@"%@", msg]; 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; } diff --git a/packages/firebase_crashlytics/example/android/app/build.gradle b/packages/firebase_crashlytics/example/android/app/build.gradle index 891334951d8d..6bd17182159e 100644 --- a/packages/firebase_crashlytics/example/android/app/build.gradle +++ b/packages/firebase_crashlytics/example/android/app/build.gradle @@ -64,5 +64,5 @@ dependencies { implementation "android.arch.lifecycle:extensions:1.1.1" } -apply plugin: 'io.fabric' apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.google.firebase.crashlytics' diff --git a/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/EmbeddingV1ActivityTest.java b/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/EmbeddingV1ActivityTest.java index 4f6a06146d7e..35f20496ee15 100644 --- a/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/EmbeddingV1ActivityTest.java +++ b/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/EmbeddingV1ActivityTest.java @@ -1,4 +1,3 @@ - package io.flutter.plugins.firebase.crashlytics.firebasecrashlytics; import androidx.test.rule.ActivityTestRule; diff --git a/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/EmbeddingV1Activity.java b/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/EmbeddingV1Activity.java index 76245da23ea0..b41a0b774914 100644 --- a/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/EmbeddingV1Activity.java +++ b/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/EmbeddingV1Activity.java @@ -1,4 +1,3 @@ - package io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample; import android.os.Bundle; diff --git a/packages/firebase_crashlytics/example/android/build.gradle b/packages/firebase_crashlytics/example/android/build.gradle index faeab902b866..4e03aa21676f 100644 --- a/packages/firebase_crashlytics/example/android/build.gradle +++ b/packages/firebase_crashlytics/example/android/build.gradle @@ -2,15 +2,12 @@ buildscript { repositories { google() jcenter() - maven { - url 'https://maven.fabric.io/public' - } } dependencies { classpath 'com.android.tools.build:gradle:3.3.2' classpath 'com.google.gms:google-services:4.3.0' - classpath 'io.fabric.tools:gradle:1.26.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0' } } diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj index b616cae76e0c..c9d085f643ad 100644 --- a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj @@ -248,7 +248,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${PODS_ROOT}/Fabric/run\"\n"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${PODS_ROOT}/FirebaseCrashlytics/run\"\n"; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; diff --git a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h b/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h index 36e21bbf9cf4..dabd2bac3daf 100644 --- a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h +++ b/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h @@ -1,6 +1,8 @@ #import #import +@import Firebase; + @interface AppDelegate : FlutterAppDelegate @end diff --git a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m b/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m index 59a72e90be12..ba737d9a8e7b 100644 --- a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m +++ b/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m @@ -5,6 +5,8 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [FIRApp configure]; + [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; diff --git a/packages/firebase_crashlytics/example/lib/main.dart b/packages/firebase_crashlytics/example/lib/main.dart index 5f2a9d04a689..e814af971722 100644 --- a/packages/firebase_crashlytics/example/lib/main.dart +++ b/packages/firebase_crashlytics/example/lib/main.dart @@ -17,9 +17,12 @@ void main() { // Pass all uncaught errors to Crashlytics. FlutterError.onError = Crashlytics.instance.recordFlutterError; - runZoned(() { - runApp(MyApp()); - }, onError: Crashlytics.instance.recordError); + runZonedGuarded( + () { + runApp(MyApp()); + }, + Crashlytics.instance.recordError, + ); } class MyApp extends StatefulWidget { @@ -46,7 +49,7 @@ class _MyAppState extends State { FlatButton( child: const Text('Key'), onPressed: () { - Crashlytics.instance.setString('foo', 'bar'); + Crashlytics.instance.setCustomKey('foo', 'bar'); }), FlatButton( child: const Text('Log'), @@ -72,7 +75,7 @@ class _MyAppState extends State { onPressed: () { // Example of an exception that does not get caught // by `FlutterError.onError` but is caught by the `onError` handler of - // `runZoned`. + // `runZonedGuarded`. Future.delayed(const 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 0169c1e736f4..4dc4e9c31338 100644 --- a/packages/firebase_crashlytics/example/test_driver/crashlytics.dart +++ b/packages/firebase_crashlytics/example/test_driver/crashlytics.dart @@ -17,13 +17,13 @@ void main() { test('recordFlutterError', () async { // This is currently only testing that we can log errors without crashing. final Crashlytics crashlytics = Crashlytics.instance; - await crashlytics.setUserName('testing'); await crashlytics.setUserIdentifier('hello'); - crashlytics.setBool('testBool', true); - crashlytics.setInt('testInt', 42); - crashlytics.setDouble('testDouble', 42.0); - crashlytics.setString('testString', 'bar'); - Crashlytics.instance.log('testing'); + crashlytics + ..setCustomKey('testBool', true) + ..setCustomKey('testInt', 42) + ..setCustomKey('testDouble', 42.0) + ..setCustomKey('testString', 'bar') + ..log('testing'); await crashlytics.recordFlutterError(FlutterErrorDetails( exception: 'testing', stack: StackTrace.fromString(''), diff --git a/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec b/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec index eb42d90940ad..46b0ca23d1c6 100644 --- a/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec +++ b/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec @@ -22,9 +22,8 @@ A new flutter plugin project. s.ios.deployment_target = '8.0' s.static_framework = true s.dependency 'Flutter' - s.dependency 'Fabric' - s.dependency 'Crashlytics' - s.dependency 'Firebase/Core' - + s.dependency 'Firebase/Crashlytics' + s.dependency 'Firebase/Analytics' + s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{libraryVersion}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-cls\\\"" } end diff --git a/packages/firebase_crashlytics/lib/firebase_crashlytics.dart b/packages/firebase_crashlytics/lib/firebase_crashlytics.dart index a57738b63f43..5f198bda8ee6 100644 --- a/packages/firebase_crashlytics/lib/firebase_crashlytics.dart +++ b/packages/firebase_crashlytics/lib/firebase_crashlytics.dart @@ -5,8 +5,6 @@ library firebase_crashlytics; import 'dart:async'; -import 'dart:collection'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; diff --git a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart b/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart index d37411ef79ad..b5873e8c246e 100644 --- a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart +++ b/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart @@ -7,18 +7,13 @@ part of firebase_crashlytics; /// /// You can get an instance by calling `Crashlytics.instance`. class Crashlytics { - static final Crashlytics instance = Crashlytics(); + static final instance = Crashlytics(); /// Set to true to have errors sent to Crashlytics while in debug mode. By /// default this is false. - bool enableInDevMode = false; + var enableInDevMode = false; - /// Keys to be included with report. - final Map _keys = {}; - - /// Logs to be included with report. - final ListQueue _logs = ListQueue(15); - int _logSize = 0; + bool get _shouldReport => !kDebugMode || enableInDevMode; @visibleForTesting static const MethodChannel channel = @@ -43,10 +38,12 @@ class Crashlytics { /// 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 { + Future recordError( + dynamic exception, + StackTrace stack, { + dynamic context, + }) async { print('Error caught by Crashlytics plugin :'); - _recordError(exception, stack, context: context); } @@ -54,67 +51,31 @@ class Crashlytics { throw StateError('Error thrown by Crashlytics plugin'); } - /// Reports the global value for debug mode. - /// TODO(kroikie): Clarify what this means in context of both Android and iOS. - Future isDebuggable() async { - final bool result = - await channel.invokeMethod('Crashlytics#isDebuggable'); - return result; - } - - /// Returns Crashlytics SDK version. - Future getVersion() async { - final String result = - await channel.invokeMethod('Crashlytics#getVersion'); - return result; + /// Add text logging that will be sent to Crashlytics. + Future log(String msg) async { + if (!_shouldReport) return; + ArgumentError.checkNotNull(msg, 'msg'); + return channel.invokeMethod( + 'Crashlytics#log', + {'log': msg}, + ); } - /// Add text logging that will be sent with your next report. `msg` will be - /// printed to the console when in debug mode. Each report has a rolling max - /// of 64k of logs, older logs are removed to allow newer logs to fit within - /// the limit. - void log(String msg) { - _logSize += Uint8List.fromList(msg.codeUnits).length; - _logs.add(msg); - // Remove oldest log till logSize is no more than 64K. - while (_logSize > 65536) { - final String first = _logs.removeFirst(); - _logSize -= Uint8List.fromList(first.codeUnits).length; - } - } - - void _setValue(String key, dynamic value) { - // Check that only 64 keys are set. - if (_keys.containsKey(key) || _keys.length <= 64) { - _keys[key] = value; - } - } - - /// Sets a value to be associated with a given key for your crash data. - void setBool(String key, bool value) { - _setValue(key, value); - } - - /// Sets a value to be associated with a given key for your crash data. - void setDouble(String key, double value) { - _setValue(key, value); - } - - /// Sets a value to be associated with a given key for your crash data. - void setInt(String key, int value) { - _setValue(key, value); - } - - /// Sets a value to be associated with a given key for your crash data. - void setString(String key, String value) { - _setValue(key, value); - } - - /// Optionally set a end-user's name or username for display within the - /// Crashlytics UI. Please be mindful of end-user's privacy. - Future setUserEmail(String email) async { - await channel.invokeMethod( - 'Crashlytics#setUserEmail', {'email': email}); + /// Sets a [value] to be associated with a given [key] for your crash data. + /// + /// The [value] will be converted to a string by calling [toString] on it. + /// An error will be thrown if it is null. + Future setCustomKey(String key, dynamic value) async { + if (!_shouldReport) return; + ArgumentError.checkNotNull(key, 'key'); + ArgumentError.checkNotNull(value, 'value'); + return channel.invokeMethod( + 'Crashlytics#setKey', + { + 'key': key, + 'value': value.toString(), + }, + ); } /// Specify a user identifier which will be visible in the Crashlytics UI. @@ -124,38 +85,6 @@ class Crashlytics { {'identifier': identifier}); } - /// Specify a user name which will be visible in the Crashlytics UI. Please - /// be mindful of end-user's privacy. - Future setUserName(String name) async { - await channel.invokeMethod( - 'Crashlytics#setUserName', {'name': name}); - } - - List> _prepareKeys() { - final List> crashlyticsKeys = >[]; - for (String key in _keys.keys) { - final dynamic value = _keys[key]; - - final Map crashlyticsKey = { - 'key': key, - 'value': value - }; - - if (value is int) { - crashlyticsKey['type'] = 'int'; - } else if (value is double) { - crashlyticsKey['type'] = 'double'; - } else if (value is String) { - crashlyticsKey['type'] = 'string'; - } else if (value is bool) { - crashlyticsKey['type'] = 'boolean'; - } - crashlyticsKeys.add(crashlyticsKey); - } - - return crashlyticsKeys; - } - @visibleForTesting List> getStackTraceElements(List lines) { final List> elements = >[]; @@ -211,12 +140,7 @@ class Crashlytics { Iterable information, bool printDetails, }) async { - bool inDebugMode = false; - if (!enableInDevMode) { - assert(inDebugMode = true); - } - - printDetails ??= inDebugMode; + printDetails ??= kDebugMode; final String _information = (information == null || information.isEmpty) ? '' @@ -237,7 +161,8 @@ class Crashlytics { // that Flutter developers are used to seeing. if (stack != null) print('\n$stack'); } - if (!inDebugMode || enableInDevMode) { + + if (_shouldReport) { // The stack trace can be null. To avoid the following exception: // Invalid argument(s): Cannot create a Trace from null. // We can check for null and provide an empty stack trace. @@ -259,8 +184,6 @@ class Crashlytics { 'context': '$context', 'information': _information, 'stackTraceElements': stackTraceElements, - 'logs': _logs.toList(), - 'keys': _prepareKeys(), }); // Print result. diff --git a/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec b/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec index 83eb735d7e80..bc5131cbba97 100644 --- a/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec +++ b/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec @@ -22,9 +22,8 @@ A new flutter plugin project. s.platform = :osx, '10.11' s.static_framework = true s.dependency 'FlutterMacOS' - s.dependency 'Fabric' - s.dependency 'Crashlytics' - s.dependency 'Firebase/Core' + s.dependency 'Firebase/Crashlytics' + s.dependency 'Firebase/Analytics' s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{libraryVersion}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-cls\\\"" } end diff --git a/packages/firebase_crashlytics/pubspec.yaml b/packages/firebase_crashlytics/pubspec.yaml index afdb095467d8..8969fc284f8d 100644 --- a/packages/firebase_crashlytics/pubspec.yaml +++ b/packages/firebase_crashlytics/pubspec.yaml @@ -2,12 +2,12 @@ name: firebase_crashlytics description: Flutter plugin for Firebase Crashlytics. It reports uncaught errors to the Firebase console. -version: 0.1.4+1 +version: 0.2.0 homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_crashlytics environment: - sdk: ">=2.0.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + sdk: ">=2.8.0 <3.0.0" + flutter: ">=1.17.0 <2.0.0" dependencies: flutter: diff --git a/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart b/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart index 59f11e023913..b19e37dd6541 100644 --- a/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart +++ b/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart @@ -1,12 +1,5 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:e2e/e2e.dart'; -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; void main() { E2EWidgetsFlutterBinding.ensureInitialized(); - - testWidgets('get version', (WidgetTester tester) async { - final String version = await Crashlytics.instance.getVersion(); - expect(version, isNotNull); - }); } diff --git a/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart b/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart index f63d34620eb8..25d707f4e4f2 100644 --- a/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart +++ b/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart @@ -12,7 +12,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$Crashlytics', () { + group('Crashlytics', () { final List log = []; final Crashlytics crashlytics = Crashlytics.instance; @@ -24,16 +24,10 @@ void main() { switch (methodCall.method) { case 'Crashlytics#onError': return 'Error reported to Crashlytics.'; - case 'Crashlytics#isDebuggable': - return true; - case 'Crashlytics#setUserEmail': - return true; case 'Crashlytics#setUserIdentifier': + case 'Crashlytics#setKey': + case 'Crashlytics#log': return true; - case 'Crashlytics#setUserName': - return true; - case 'Crashlytics#getVersion': - return '0.0.0+1'; default: return false; } @@ -41,6 +35,25 @@ void main() { log.clear(); }); + test('log', () async { + crashlytics.enableInDevMode = true; + final msg = 'foo'; + await crashlytics.log(msg); + expect(log[0].method, 'Crashlytics#log'); + expect(log[0].arguments['log'], msg); + }); + + test('setKeys', () async { + crashlytics.enableInDevMode = true; + final key = 'testKey'; + // All values are converted to Strings by Dart code + final value = 'testValue'; + await crashlytics.setCustomKey(key, value); + expect(log[0].method, 'Crashlytics#setKey'); + expect(log[0].arguments['key'], key); + expect(log[0].arguments['value'], value); + }); + test('recordFlutterError', () async { final FlutterErrorDetails details = FlutterErrorDetails( exception: 'foo exception', @@ -53,35 +66,15 @@ void main() { context: ErrorDescription('foo context'), ); crashlytics.enableInDevMode = true; - crashlytics.log('foo'); - crashlytics.setBool('testBool', true); - crashlytics.setInt('testInt', 42); - crashlytics.setDouble('testDouble', 42.0); - crashlytics.setString('testString', 'bar'); await crashlytics.recordFlutterError(details); expect(log[0].method, 'Crashlytics#onError'); expect(log[0].arguments['exception'], 'foo exception'); expect(log[0].arguments['context'], 'foo context'); expect(log[0].arguments['information'], 'test message\nsecond message'); - 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('recordError', () async { crashlytics.enableInDevMode = true; - crashlytics.log('foo'); await crashlytics.recordError('foo exception', null, context: "context"); expect(log[0].method, 'Crashlytics#onError'); expect(log[0].arguments['exception'], 'foo exception'); @@ -91,53 +84,12 @@ void main() { log[0].arguments['stackTraceElements'], contains(containsPair('file', 'firebase_crashlytics_test.dart')), ); - 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( - log, - [ - isMethodCall( - 'Crashlytics#isDebuggable', - arguments: null, - ) - ], - ); }); test('crash', () { expect(() => crashlytics.crash(), throwsStateError); }); - test('getVersion', () async { - await crashlytics.getVersion(); - expect(log, - [isMethodCall('Crashlytics#getVersion', arguments: null)]); - }); - - test('setUserEmail', () async { - await crashlytics.setUserEmail('foo'); - expect(log, [ - isMethodCall('Crashlytics#setUserEmail', - arguments: {'email': 'foo'}) - ]); - }); - test('setUserIdentifier', () async { await crashlytics.setUserIdentifier('foo'); expect(log, [ @@ -146,14 +98,6 @@ void main() { ]); }); - test('setUserName', () async { - await crashlytics.setUserName('foo'); - expect(log, [ - isMethodCall('Crashlytics#setUserName', - arguments: {'name': 'foo'}) - ]); - }); - test('getStackTraceElements with character index', () async { final List lines = [ 'package:flutter/src/widgets/framework.dart 3825:27 StatefulElement.build'