diff --git a/.github/workflows/firebase_crashlytics.yaml b/.github/workflows/firebase_crashlytics.yaml new file mode 100644 index 000000000000..edd82527fcd1 --- /dev/null +++ b/.github/workflows/firebase_crashlytics.yaml @@ -0,0 +1,62 @@ +name: firebase_crashlytics + +on: + pull_request: + paths: + - "packages/firebase_crashlytics/**" + - ".github/workflows/firebase_crashlytics.yaml" + push: + branches: + - master + paths-ignore: + - "docs/**" + +env: + FLUTTERFIRE_PLUGIN_SCOPE: "*firebase_crashlytics*" + FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE: "*firebase_crashlytics_example*" + +jobs: + android: + if: github.event_name == 'pull_request' + runs-on: macos-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: "Install Flutter" + run: ./.github/workflows/scripts/install-flutter.sh stable + - name: "Install Tools" + run: ./.github/workflows/scripts/install-tools.sh + - name: "Build Example" + run: ./.github/workflows/scripts/build-example.sh android + - name: "Drive Example" + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + arch: x86_64 + target: google_apis + profile: Nexus 5X + script: ./.github/workflows/scripts/drive-example.sh android + + apple: + runs-on: macos-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: "Install Flutter" + run: ./.github/workflows/scripts/install-flutter.sh dev + - name: "Install Tools" + run: | + ./.github/workflows/scripts/install-tools.sh + flutter config --enable-macos-desktop + - name: "Build iOS Example" + run: ./.github/workflows/scripts/build-example.sh ios + - name: "Drive iOS Example" + run: ./.github/workflows/scripts/drive-example.sh ios + - name: "Build MacOS Example" + run: ./.github/workflows/scripts/build-example.sh macos + - name: "Drive MacOS Example" + run: ./.github/workflows/scripts/drive-example.sh macos diff --git a/docs/_assets/crashlytics-example-detail.png b/docs/_assets/crashlytics-example-detail.png new file mode 100644 index 000000000000..c4ca9f867569 Binary files /dev/null and b/docs/_assets/crashlytics-example-detail.png differ diff --git a/docs/_assets/crashlytics-example.png b/docs/_assets/crashlytics-example.png new file mode 100644 index 000000000000..13746641a90c Binary files /dev/null and b/docs/_assets/crashlytics-example.png differ diff --git a/docs/_assets/crashlytics-filter.png b/docs/_assets/crashlytics-filter.png new file mode 100644 index 000000000000..e536e002ca41 Binary files /dev/null and b/docs/_assets/crashlytics-filter.png differ diff --git a/docs/crashlytics/overview.mdx b/docs/crashlytics/overview.mdx new file mode 100644 index 000000000000..560eaa04cdc2 --- /dev/null +++ b/docs/crashlytics/overview.mdx @@ -0,0 +1,82 @@ +--- +title: Crashlytics +sidebar_label: Overview +--- + +## What does it do? + +Crashlytics helps you to collect analytics and details about crashes and errors that occur in your app. It does this through three aspects: + +- **Logs**: Log events in your app to be sent with the crash report for context if your app crashes. +- **Crash reports**: Every crash is automatically turned into a crash report and sent when the application next opens. +- **Stack traces**: Even when an error is caught and your app recovers, the Dart stack trace can still be sent. + + + +## Installation + +### 1. Add dependency + +```yaml {5} title="pubspec.yaml" +dependencies: + flutter: + sdk: flutter + firebase_core: "^{{ plugins.firebase_core }}" + firebase_crashlytics: "^{{ plugins.firebase_crashlytics }}" +``` + +### 2. Download dependency + +``` +$ flutter pub get +``` + +### 3. Platform integration + +If you are migrating from a previous version of Crashlytics that used Fabric, please follow these +guides and remove any legacy Fabric integration steps from your project: + + - [Android](https://firebase.google.com/docs/crashlytics/upgrade-sdk?platform=android). + - [iOS/macOS](https://firebase.google.com/docs/crashlytics/upgrade-sdk?platform=ios). + +#### a. Android + +1. Add the following classpaths to your `android/build.gradle` file. + +```groovy {3} title="android/build.gradle" +dependencies { + // ... other dependencies + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' +} +``` + +2. Apply the following to the bottom of your `android/app/build.gradle` file. + +```groovy {5} title="android/app/build.gradle" +dependencies { + // ... your dependencies +} + +apply plugin: 'com.google.firebase.crashlytics' +``` + +#### b. iOS + +1. From Xcode select `Runner` from the project navigation. +2. Select the `Build Phases` tab, then click `+ > New Run Script Phase`. +3. Add `${PODS_ROOT}/FirebaseCrashlytics/run` to the `Type a script...` text box. +4. Optionally you can also provide your app's built `Info.plist` location to the build phase's Input Files field: +For example: `$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)` + +### 4. Rebuild your app + +Once complete, rebuild your Flutter application: + +```bash +$ flutter run +``` + +## Next steps + +Once installed, you're ready to start using Firebase Crashlytics in your Flutter Project. View the +[Usage documentation](./usage.mdx) to get started. diff --git a/docs/crashlytics/reports.mdx b/docs/crashlytics/reports.mdx new file mode 100644 index 000000000000..2deb99e832e6 --- /dev/null +++ b/docs/crashlytics/reports.mdx @@ -0,0 +1,63 @@ +--- +title: Crashlytics in the Firebase Console +sidebar_label: Viewing crash reports +--- + +## Overview + +Once you have Crashlytics setup, you can navigate to Crashlytics in your Firebase Console underneath 'Quality'. +If this page tells you to setup, build or run your app, then you have not correctly setup Crashlytics in your app (see [Usage](usage)) + +> Crashlytics is enabled by default, to disable it in debug mode (recommended) or for opt-in purposes see [toggle Crashlytics collection.](usage#toggle-crashlytics-collection) + +When using any of the examples under [usage](usage), you will be shown a dashboard similar to the following: + +![hide:Crashlytics Dashboard Example](crashlytics-example.png) + +Here you can see that the crash was ran 4 times in the last 60 minutes. + +> Error reports are only uploaded to Crashlytics upon relaunching the app + +## Issues + +The issues section of the Crashlytics console shows all the reports from your app, and organizes them into separate issues. +One of the issues above originates from `FirebaseCrashlyticsPlugin.java`, which is the module responsible for testing the +Crashlytics [`crash`](usage#forcing-a-crash) method by throwing an uncaught exception to crash the app. Using the [`crash`](usage#forcing-a-crash) method on the same platform +will always add reports to the same issue. + +## Filtering crash types + +The Crashlytics console allows you to filter your issues by event types such as `Crashes` and `Non-fatals`. +In the below example, you can see our project filtered by `Non-fatal` crashes. + +![hide:Crashlytics Filter Example](crashlytics-filter.png) + +In the issues section, it shows us that the event was triggered in the `mobile.dart` file on line 62, which is the below +code snippet: + +```dart highlight={2} title="mobile.dart" +try { + throw 'error_example'; +} catch (e, s) { + FirebaseCrashlytics.instance.recordError(e, s, context: 'as an example'); +} +``` + +## Managing issues + +By clicking on an issue, you can view its statistics and associated reports in more detail. The below screenshot shows the +issue relating to `FirebaseCrashlyticsPlugin.java` + +![hide:Crashlytics Issue Example](crashlytics-example-detail.png) + +Here you can see a breakdown of the issue, that shows events by versions, devices and operating systems. Below that, you +can browser specific reports and view their content. In our example, you can see that we have used the [`setCustomKey`](usage#add-custom-keys) method +to add in our custom attributes. Under the log section, you can see any messages added by the [`log`](usage#add-custom-log-messages') method + +## Closing issues + +At the top right of the issue, there is a button that says "Close." After addressing the issue, you can close it, allowing +you to filter this out on the dashboard. When the same issue re-occurs, it will automatically open again. If you want to +prevent this from happening, click the drop down arrow next to the button and select mute. + + diff --git a/docs/crashlytics/usage.mdx b/docs/crashlytics/usage.mdx index 31a5ecde8d05..6b8bff7c15b0 100644 --- a/docs/crashlytics/usage.mdx +++ b/docs/crashlytics/usage.mdx @@ -1,6 +1,172 @@ --- -title: Crashlytics +title: Using Firebase Crashlytics sidebar_label: Usage --- -Crashlytics usage +To start using Firebase Crashlytics within your project, import it at the top of your project files: + +```dart +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +``` + +## Sending reports to Crashlytics + +To send report data to Crashlytics, the **application must be restarted**. Crashlytics automatically sends any crash reports +to Firebase the next time the application is launched. + +## Toggle Crashlytics collection + +Call the `setCrashlyticsCollectionEnabled` method to toggle Crashlytics collection status. + +For example to ensure it is disabled when your app is in debug mode you can do the following: + +```dart +import 'package:flutter/foundation.dart' show kDebugMode; + +// ... + +if (kDebugMode) { + // Force disable Crashlytics collection while doing every day development. + // Temporarily toggle this to true if you want to test crash reporting in your app. + await FirebaseCrashlytics.instance + .setCrashlyticsCollectionEnabled(false); +} else { + // Handle Crashlytics enabled status when not in Debug, + // e.g. allow your users to opt-in to crash reporting. +} +``` + +You can additionally read the current collection enabled status: + +```dart +if (FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled) { + // Collection is enabled. +} +``` + +## Forcing a crash + +You don't have to wait for a crash to know that Crashlytics is working. To force a crash, call the `crash` method: + +```dart +FirebaseCrashlytics.instance.crash(); +``` + +Your app should exit immediately after calling this method. After opening your app again after the crash +Firebase Crashlytics will upload the crash report to the Firebase Console. + +The error will be show on the Firebase Crashlytics dashboard as an instance of `FirebaseCrashlyticsTestCrash`, with a message of +`This is a test crash caused by calling .crash() in Dart.` + +## Add custom keys + +To associate key/value pairs with your crash reports, you can use the `setCustomKey` method + +```dart +// Set a key to a string. +FirebaseCrashlytics.instance.setCustomKey('str_key', 'hello'); + +// Set a key to a boolean. +FirebaseCrashlytics.instance.setCustomKey("bool_key", true); + +// Set a key to an int. +FirebaseCrashlytics.instance.setCustomKey("int_key", 1); + +// Set a key to a long. +FirebaseCrashlytics.instance.setCustomKey("int_key", 1L); + +// Set a key to a float. +FirebaseCrashlytics.instance.setCustomKey("float_key", 1.0f); + +// Set a key to a double. +FirebaseCrashlytics.instance.setCustomKey("double_key", 1.0); +``` + +> This accepts a maximum of 64 key/value pairs. New keys beyond that limit are ignored. Keys or values that exceed 1024 characters are truncated. + +## Add custom log messages + +To add custom Crashlytics log messages to your app, use the `log` method + +```dart +FirebaseCrashlytics.instance.log("Higgs-Boson detected! Bailing out"); +``` + +## Set user identifiers + +To add user IDs to your reports, assign each user with a unique ID. This can be an ID number, token or hashed value: + +```dart +FirebaseCrashlytics.instance.setUserIdentifier("12345"); +``` + +## Handling uncaught errors + +By overriding `FlutterError.onError` with `FirebaseCrashlytics.instance.recordFlutterError`, it will automatically +catch all errors that are thrown within the Flutter framework. + +```dart +void main() { + // Pass all uncaught errors from the framework to Crashlytics. + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; + + runApp(MyApp()); +} +``` +### Zoned Errors + +If you want to catch errors that occur in zones, you can pass `FirebaseCrashlytics.instance.recordError` to the second +parameter of `runZonedGuarded` + +```dart +runZonedGuarded>(() async { + // ... + }, FirebaseCrashlytics.instance.recordError); + ``` + +### Errors outside of Flutter + +To catch errors that happen outside of the Flutter context, install an error listener on the current Isolate: + +```dart +Isolate.current.addErrorListener(RawReceivePort((pair) async { + final List errorAndStacktrace = pair; + await FirebaseCrashlytics.instance.recordError( + errorAndStacktrace.first, + errorAndStacktrace.last, + ); +}).sendPort); +``` + +## Enable opt-in reporting + +By default, Crashlytics will automatically collect crash reports for all your app's users. To give users more control over +the data they send, you can enable opt-in reporting by disabling automatic collection and initializing Crashlytics only for selected users: + +1. Turn off automatic collection natively: + +a. Android +In the `application` block of your `AndroidManifest.xml` file, add a `meta-data` tags to turn off automatic collection: + +```xml + +``` + +b. iOS + +Add a new key to your `Info.plist` file. + +- Key: `FirebaseCrashlyticsCollectionEnabled` +- Value: `false` + +2. Enable collection for select users by calling the Crashlytics data collection override at runtime. To opt out of automatic +crash reporting, pass `false` as the override value. When set to `false`, the new value does not apply until the next run +of the app. + +```dart +FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); +``` + + diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index a25c69dad600..abecc49694f5 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -12,11 +12,12 @@ to upgrade to the latest recommended versions, follow this guide to help ease th The table below shows the recommended package versions for each plugin this migration guide covers. -| Plugin | Version | -| ------------------: | -------------------------------: | -| `cloud_firestore` | `^{{ plugins.cloud_firestore }}` | -| `firebase_auth` | `^{{ plugins.firebase_auth }}` | -| `firebase_core` | `^{{ plugins.firebase_core }}` | +| Plugin | Version | +| ----------------------: | ------------------------------------: | +| `cloud_firestore` | `^{{ plugins.cloud_firestore }}` | +| `firebase_auth` | `^{{ plugins.firebase_auth }}` | +| `firebase_core` | `^{{ plugins.firebase_core }}` | +| `firebase_crashlytics` | `^{{ plugins.firebase_crashlytics }}` | ## Breaking Changes & Deprecations @@ -46,13 +47,14 @@ Update the Firebase plugins you use in your project to versions that are compati Edit the plugin versions in your `pubspec.yaml` file within your project to match the versions below: -```yaml {6,7} title="pubspec.yaml" +```yaml {6,7,8} title="pubspec.yaml" dependencies: flutter: sdk: flutter firebase_core: "^{{ plugins.firebase_core }}" # Newly reworked plugins covered by this migration guide: firebase_auth: "^{{ plugins.firebase_auth }}" + firebase_crashlytics: "^{{ plugins.firebase_crashlytics }}" cloud_firestore: "^{{ plugins.cloud_firestore }}" # Updated to work with new core only plugins (no new changes): cloud_functions: "^{{ plugins.cloud_functions }}" @@ -95,11 +97,23 @@ show you how to set up a default Firebase application: - e.g. remove any lines similar to: `implementation 'com.google.firebase:firebase-{PRODUCT}:{VERSION}` - The reworked plugins now automatically manage the versions of the Firebase SDKs used internally so these are no longer required. - See the [overriding native sdk versions documentation](overview#overriding-native-sdk-versions) if this is something you require. + - **Crashlytics only**: Remove the `maven { url 'https://maven.fabric.io/public' }` line from your projects `android/build.gradle` file. + - **Crashlytics only**: Remove the `classpath 'io.fabric.tools:gradle:1.31.2'` line from your projects `android/build.gradle` file. + - **Crashlytics only**: Remove the `apply plugin: 'io.fabric'` line from your projects `android/app/build.gradle` file. + - **Crashlytics only**: Remove any dependencies referencing `*com.crashlytics.sdk.android:crashlytics*` line from your projects `android/app/build.gradle` file (inside the `dependencies { .. }` block). + - **Crashlytics only**: Follow the new Android integration steps shown in the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#a-android). #### B. [iOS Installation](installation/ios.mdx) - - If you manually configured the `[FIRApp configure];` or `FirebaseApp.configure()` within your projects `ios/{projectName}/AppDelegate{.m/.swift}` file, +These steps can also apply to macOS. + + - If you manually import Firebase and configure it via `[FIRApp configure];` or `FirebaseApp.configure()` within your projects `ios/{projectName}/AppDelegate{.m/.swift}` file, you can now safely remove it - FlutterFire automatically handles this for you. + - **Crashlytics only**: Open your `ios/Runner.xcworkspace` workspace file with Xcode. + 1. From Xcode select `Runner` from the left hand side project navigation. + 2. Select the `Build Phases` tab in the middle pane. + 3. Find a script in the list of Build Phases that contains `${PODS_ROOT}/Fabric/run` as the shell script. Once located, press the `x` on the right of the row to remove that script. If you can't find one then you can ignore this step. + - **Crashlytics only**: Follow the new iOS integration steps shown in the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#b-ios). - If you face CocoaPods issues when building, follow the steps below in the [CocoaPods could not find compatible versions for pod](#cocoapods-could-not-find-compatible-versions-for-pod) section. #### C. [Web Installation](installation/web.mdx) @@ -175,6 +189,45 @@ To learn more about secondary Firebase app instances, view the [Initializing sec - Android: Removed Gradle ‘hacks’ and upgrade Flutter SDK requirement from `>=1.12.13+hotfix.4` to `>=1.12.13+hotfix.5` - based on PR https://github.com/flutter/plugins/pull/2651 - Android: Switched to using Firebase BoM to manage SDK versions +### Crashlytics + +- **BREAKING**: Removal of Fabric SDKs and migration to the new Firebase Crashlytics SDK. +- **BREAKING**: The following methods have been removed as they are no longer available on the Firebase Crashlytics SDK: + - `setUserEmail` + - `setUserName` + - `getVersion` + - `isDebuggable` +- **BREAKING**: `log` now returns a Future. Calling `log` now sends logs immediately to the underlying Crashlytics SDK instead of pooling them in Dart. +- **BREAKING**: the methods `setInt`, `setDouble`, `setString` and `setBool` have been replaced by `setCustomKey`. + - `setCustomKey` returns a Future. Calling `setCustomKey` now sends custom keys immediately to the underlying Crashlytics SDK instead of pooling them in Dart. +- **DEPRECATED**: `enableInDevMode` has been deprecated, use `isCrashlyticsCollectionEnabled` and `setCrashlyticsCollectionEnabled` instead. +- **DEPRECATED**: `Crashlytics` has been deprecated, use `FirebaseCrashlytics` instead. +- **NEW**: Custom keys that are automatically added by FlutterFire when calling `reportError` are now prefixed with `flutter_error_`. +- **NEW**: Calling `.crash()` on Android & iOS/macOS now reports a custom named exception to the Firebase Console. This allows you to easily locate test crashes. + - Name: `FirebaseCrashlyticsTestCrash`. + - Message: `This is a test crash caused by calling .crash() in Dart.`. +- **NEW**: `recordError` now uses a named native exception when reporting to the Firebase Console. This allows you to easily locate errors originating from Flutter. + - Name: `FlutterError`. +- **NEW**: Added support for `checkForUnsentReports`. + - Checks a device for any fatal or non-fatal crash reports that haven't yet been sent to Crashlytics. + - See reference API docs for more information. +- **NEW**: Added support for `deleteUnsentReports`. + - If automatic data collection is disabled, this method queues up all the reports on a device for deletion. + - See reference API docs for more information. +- **NEW**: Added support for `didCrashOnPreviousExecution`. + - Checks whether the app crashed on its previous run. + - See reference API docs for more information. +- **NEW**: Added support for `sendUnsentReports`. + - If automatic data collection is disabled, this method queues up all the reports on a device to send to Crashlytics. + - See reference API docs for more information. +- **NEW**: Added support for `setCrashlyticsCollectionEnabled`. + - Enables/disables automatic data collection by Crashlytics. + - See reference API docs for more information. +- **NEW**: Added support for `isCrashlyticsCollectionEnabled`. + - Whether the current Crashlytics instance is collecting reports. If false, then no crash reporting data is sent to Firebase. + - See reference API docs for more information. +- **FIX**: Fixed a bug that prevented keys from being set on iOS devices. + ### Firestore Along with the below changes, the plugin has undergone a quality of life update to better support exceptions thrown. diff --git a/docs/sidebars.js b/docs/sidebars.js index 3521cbd1f121..dd1600ee8c8d 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -35,7 +35,7 @@ module.exports = { "Cloud Messaging": ["messaging/overview", "messaging/ios-integration", toReferenceAPI("firebase_messaging")], "Cloud Storage": ["storage/overview", toReferenceAPI("firebase_storage")], Core: ["core/usage", toReferenceAPI("firebase_core")], - // Crashlytics: ["crashlytics/usage", toReferenceAPI("firebase_crashlytics")], + Crashlytics: ["crashlytics/overview", "crashlytics/usage", "crashlytics/reports", toReferenceAPI("firebase_crashlytics")], "Realtime Database": ["database/overview", toReferenceAPI("firebase_database")], // "Dynamic Links": ["dynamic-links/usage", toReferenceAPI("firebase_dynamic_links")], // "Instance ID": ["iid/usage", toReferenceAPI("firebase_in_app_messaging")], diff --git a/packages/firebase_crashlytics/.metadata b/packages/firebase_crashlytics/.metadata deleted file mode 100644 index fbc1f3952e5d..000000000000 --- a/packages/firebase_crashlytics/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b - channel: beta - -project_type: plugin diff --git a/packages/firebase_crashlytics/README.md b/packages/firebase_crashlytics/README.md deleted file mode 100644 index 1f7a7dc8fc6e..000000000000 --- a/packages/firebase_crashlytics/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# firebase_crashlytics plugin - -A Flutter plugin to use the [Firebase Crashlytics Service](https://firebase.google.com/docs/crashlytics/). - -[![pub package](https://img.shields.io/pub/v/firebase_crashlytics.svg)](https://pub.dev/packages/firebase_crashlytics) - -For Flutter plugins for other Firebase products, see [README.md](https://github.com/FirebaseExtended/flutterfire/blob/master/README.md). - -## Usage - -### Import the firebase_crashlytics plugin - -To use the `firebase_crashlytics` plugin, follow the [plugin installation instructions](https://pub.dev/packages/firebase_crashlytics#pub-pkg-tab-installing). - -### 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' - } -} -``` - -2. Add the following classpaths to the `[project]/android/build.gradle` file. -```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' -} -``` - -2. Apply the following plugins in the `[project]/android/app/build.gradle` file. -```gradle -// ADD THIS AT THE BOTTOM -apply plugin: 'io.fabric' -apply plugin: 'com.google.gms.google-services' -``` - -*Note:* If this section is not completed, you will get an error like this: -``` -java.lang.IllegalStateException: -Default FirebaseApp is not initialized in this process [package name]. -Make sure to call FirebaseApp.initializeApp(Context) first. -``` - -*Note:* When you are debugging on Android, use a device or AVD with Google Play services. -Otherwise, you will not be able to use Firebase Crashlytics. - -### iOS Integration - -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. 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 - // This is only to be used for confirming that reports are being - // submitted as expected. It is not intended to be used for everyday - // development. - Crashlytics.instance.enableInDevMode = true; - - // Pass all uncaught errors from the framework to Crashlytics. - FlutterError.onError = Crashlytics.instance.recordFlutterError; - - runApp(MyApp()); -} -``` - -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: -```dart -runZoned>(() async { - // ... - }, onError: Crashlytics.instance.recordError); -``` - -Finally, to catch errors that happen outside Flutter context, install an error -listener on the current Isolate: - -```dart -Isolate.current.addErrorListener(RawReceivePort((pair) async { - final List errorAndStacktrace = pair; - await Crashlytics.instance.recordError( - errorAndStacktrace.first, - errorAndStacktrace.last, - ); -}).sendPort); -``` - -## Result - -If an error is caught, you should see the following messages in your logs: -``` -flutter: Flutter error caught by Crashlytics plugin: -// OR if you use recordError for runZoned: -flutter: Error caught by Crashlytics plugin : -// Exception, context, information, and stack trace in debug mode -// OR if not in debug mode: -flutter: Error reported to Crashlytics. -``` - -*Note:* It may take awhile (up to 24 hours) before you will be able to see the logs appear in your Firebase console. - -## Example - -See the [example application](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_crashlytics/example) source -for a complete sample app using `firebase_crashlytics`. - -## Issues and feedback - -Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). - -Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). - -To contribute a change to this plugin, -please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) -and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_crashlytics/analysis_options.yaml b/packages/firebase_crashlytics/analysis_options.yaml deleted file mode 100644 index 255ec37c361d..000000000000 --- a/packages/firebase_crashlytics/analysis_options.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - curly_braces_in_flow_control_structures: ignore - public_member_api_docs: ignore - unawaited_futures: ignore diff --git a/packages/firebase_crashlytics/android/build.gradle b/packages/firebase_crashlytics/android/build.gradle deleted file mode 100644 index a557dc49e8a2..000000000000 --- a/packages/firebase_crashlytics/android/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -group 'io.flutter.plugins.firebase.crashlytics.firebasecrashlytics' -version '1.0-SNAPSHOT' - -buildscript { - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - maven { - url 'https://maven.fabric.io/public' - } - } -} - -apply plugin: 'com.android.library' - -android { - compileSdkVersion 28 - - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } -} - -dependencies { - implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' - implementation 'com.google.firebase:firebase-common:16.1.0' - implementation 'androidx.annotation:annotation:1.1.0' -} - -apply from: file("./user-agent.gradle") - -// TODO(): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 -afterEvaluate { - def containsEmbeddingDependencies = false - for (def configuration : configurations.all) { - for (def dependency : configuration.dependencies) { - if (dependency.group == 'io.flutter' && - dependency.name.startsWith('flutter_embedding') && - dependency.isTransitive()) - { - containsEmbeddingDependencies = true - break - } - } - } - if (!containsEmbeddingDependencies) { - android { - dependencies { - def lifecycle_version = "1.1.1" - implementation "android.arch.lifecycle:runtime:$lifecycle_version" - implementation "android.arch.lifecycle:common:$lifecycle_version" - implementation "android.arch.lifecycle:common-java8:$lifecycle_version" - implementation "android.arch.lifecycle:extensions:$lifecycle_version" - } - } - } -} 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 deleted file mode 100644 index 6de881d1da50..000000000000 --- a/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FirebaseCrashlyticsPlugin.java +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.crashlytics.firebasecrashlytics; - -import android.content.Context; -import android.util.Log; -import com.crashlytics.android.Crashlytics; -import io.fabric.sdk.android.Fabric; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** FirebaseCrashlyticsPlugin */ -public class FirebaseCrashlyticsPlugin implements FlutterPlugin, MethodCallHandler { - public static final String TAG = "CrashlyticsPlugin"; - private MethodChannel channel; - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - BinaryMessenger binaryMessenger = binding.getBinaryMessenger(); - channel = setup(binaryMessenger, binding.getApplicationContext()); - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - if (channel != null) { - channel.setMethodCallHandler(null); - channel = null; - } - } - - private static MethodChannel setup(BinaryMessenger binaryMessenger, Context context) { - 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; - } - - /** Plugin registration. */ - public static void registerWith(Registrar registrar) { - setup(registrar.messenger(), registrar.context()); - } - - @Override - public void onMethodCall(MethodCall call, Result result) { - 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<>(); - for (Map errorElement : errorElements) { - 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")); - - // 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); - - // Log information. - final String information = call.argument("information"); - if (information != null && !information.isEmpty()) Crashlytics.log(information); - - Crashlytics.logException(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")); - result.success(null); - } else { - result.notImplemented(); - } - } - - /** - * Extract StackTraceElement from Dart stack trace element. - * - * @param errorElement Map representing the parts of a Dart error. - * @return Stack trace element to be used as part of an Exception stack trace. - */ - private StackTraceElement generateStackTraceElement(Map errorElement) { - try { - String fileName = errorElement.get("file"); - String lineNumber = errorElement.get("line"); - String className = errorElement.get("class"); - String methodName = errorElement.get("method"); - - return new StackTraceElement( - className == null ? "" : className, methodName, fileName, Integer.parseInt(lineNumber)); - } catch (Exception e) { - Log.e(TAG, "Unable to generate stack trace element from Dart side error."); - return null; - } - } -} diff --git a/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m deleted file mode 100644 index 607f4def8f02..000000000000 --- a/packages/firebase_crashlytics/darwin/Classes/FirebaseCrashlyticsPlugin.m +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FirebaseCrashlyticsPlugin.h" - -#import - -@interface FirebaseCrashlyticsPlugin () -@property(nonatomic, retain) FlutterMethodChannel *channel; -@end - -@implementation FirebaseCrashlyticsPlugin -+ (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_crashlytics" - binaryMessenger:[registrar messenger]]; - 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]; - } -} - -- (instancetype)init { - self = [super init]; - if (self) { - if (![FIRApp defaultApp]) { - [FIRApp configure]; - } - } - return self; -} - -- (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); - } - - // Report crash. - NSArray *errorElements = call.arguments[@"stackTraceElements"]; - NSMutableArray *frames = [NSMutableArray array]; - for (NSDictionary *errorElement in errorElements) { - [frames addObject:[self generateFrame:errorElement]]; - } - - NSString *context = call.arguments[@"context"]; - NSString *reason; - if (context != nil) { - reason = [NSString stringWithFormat:@"thrown %@", context]; - } - - [[Crashlytics sharedInstance] recordCustomExceptionName:call.arguments[@"exception"] - reason:reason - frameArray:frames]; - 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"]]; - 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]; - - return frame; -} - -@end diff --git a/packages/firebase_crashlytics/example/README.md b/packages/firebase_crashlytics/example/README.md deleted file mode 100644 index 6b284ab319b7..000000000000 --- a/packages/firebase_crashlytics/example/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# firebase_crashlytics_example - -Demonstrates how to use the firebase_crashlytics plugin. - -## Getting Started - -### Add your own Firebase project - -This example initially uses a default project for CI purposes. You must -replace the default project with your own so that you can review the error -reports submitted to the Firebase console. - -See [docs](https://firebase.google.com/docs/flutter/setup) for how to add -Firebase to a Flutter project. - -For further help getting started with Flutter, view our online -[documentation](http://flutter.io/). diff --git a/packages/firebase_crashlytics/example/android/app/google-services.json b/packages/firebase_crashlytics/example/android/app/google-services.json deleted file mode 100644 index 4f8ed237b31b..000000000000 --- a/packages/firebase_crashlytics/example/android/app/google-services.json +++ /dev/null @@ -1,545 +0,0 @@ -{ - "project_info": { - "project_number": "380450695418", - "firebase_url": "https://fir-for-flutter-xry.firebaseio.com", - "project_id": "firebase-for-flutter-xry", - "storage_bucket": "firebase-for-flutter-xry.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:df22ebedafef81e5", - "android_client_info": { - "package_name": "com.example.myapp" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:c1f0f09aa42eba16", - "android_client_info": { - "package_name": "com.google.firebase.example.fireeats" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-680tdukm5lmsvqdgfjf35oooapesp5qe.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.example.fireeats", - "certificate_hash": "13b46061adbac6fccea79893f1eeff8f94b17318" - } - }, - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" - } - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:cdde97911ac112a2", - "android_client_info": { - "package_name": "com.google.firehunt" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-56dso58q89k1u1g4jr2csjvu0t0s5sah.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firehunt", - "certificate_hash": "13b46061adbac6fccea79893f1eeff8f94b17318" - } - }, - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" - } - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:66cfa0cc55000185", - "android_client_info": { - "package_name": "io.flutter.plugins.crashlytics.crashlyticsexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:7521d73664dc56fc", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.cloudfunctions.cloudfunctionsexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:534a419926d9c345", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:b46d52db5a4d815e", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.firebaseremoteconfig" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:6ef94ae486218531", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.firebaseremoteconfigexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:236f9daea101f77e", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.firestoreexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-s5i0ce3nvtebsqc43fdjq7rub7m5kl52.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "io.flutter.plugins.firebase.firestoreexample", - "certificate_hash": "1da568611fe7dcfeafc0f35ad42f585c0a32012d" - } - }, - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" - } - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:faccb3c5e155ce04", - "android_client_info": { - "package_name": "io.flutter.plugins.firebase.mlkit.firebasemlkitexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:11e8f037a4a3ec3b", - "android_client_info": { - "package_name": "io.flutter.plugins.firebaseauthexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-1r77qvo64360tkatfaimd7jsge6ookui.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "io.flutter.plugins.firebaseauthexample", - "certificate_hash": "13b46061adbac6fccea79893f1eeff8f94b17318" - } - }, - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" - } - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:c68d3ad04a4046db", - "android_client_info": { - "package_name": "io.flutter.plugins.firebasedatabaseexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-nva3tg4js1gmdrm0egas2dlbj3go6nsk.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "io.flutter.plugins.firebasedatabaseexample", - "certificate_hash": "1da568611fe7dcfeafc0f35ad42f585c0a32012d" - } - }, - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" - } - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:620f0e4ca16cbddd", - "android_client_info": { - "package_name": "io.flutter.plugins.firebasemessagingexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:380450695418:android:ef48439a0cc0263d", - "android_client_info": { - "package_name": "io.flutter.plugins.firebasestorageexample" - } - }, - "oauth_client": [ - { - "client_id": "380450695418-17gd38vnvfrt9ak76bfdeu4jsb0h6is5.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyD-qW3X8rktjbW3BPEX8KlpqYNCUdlTtGQ" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file 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 deleted file mode 100644 index 4f6a06146d7e..000000000000 --- a/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/EmbeddingV1ActivityTest.java +++ /dev/null @@ -1,15 +0,0 @@ - -package io.flutter.plugins.firebase.crashlytics.firebasecrashlytics; - -import androidx.test.rule.ActivityTestRule; -import dev.flutter.plugins.e2e.FlutterTestRunner; -import io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample.EmbeddingV1Activity; -import org.junit.Rule; -import org.junit.runner.RunWith; - -@RunWith(FlutterTestRunner.class) -public class EmbeddingV1ActivityTest { - @Rule - public ActivityTestRule rule = - new ActivityTestRule<>(EmbeddingV1Activity.class); -} diff --git a/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/MainActivityTest.java b/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/MainActivityTest.java deleted file mode 100644 index 76aeeea0f7b3..000000000000 --- a/packages/firebase_crashlytics/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/MainActivityTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.flutter.plugins.firebase.crashlytics.firebasecrashlytics; - -import androidx.test.rule.ActivityTestRule; -import dev.flutter.plugins.e2e.FlutterTestRunner; -import io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample.MainActivity; -import org.junit.Rule; -import org.junit.runner.RunWith; - -@RunWith(FlutterTestRunner.class) -public class MainActivityTest { - @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); -} diff --git a/packages/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 2d8b4785bcd4..000000000000 --- a/packages/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - 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 deleted file mode 100644 index 76245da23ea0..000000000000 --- a/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/EmbeddingV1Activity.java +++ /dev/null @@ -1,14 +0,0 @@ - -package io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample; - -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class EmbeddingV1Activity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/MainActivity.java b/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/MainActivity.java deleted file mode 100644 index fed26b325298..000000000000 --- a/packages/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlyticsexample/MainActivity.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample; - -import dev.flutter.plugins.e2e.E2EPlugin; -import io.flutter.embedding.android.FlutterActivity; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.plugins.firebase.crashlytics.firebasecrashlytics.FirebaseCrashlyticsPlugin; - -public class MainActivity extends FlutterActivity { - // TODO(): Remove this once v2 of GeneratedPluginRegistrant - // rolls to stable. https://github.com/flutter/flutter/issues/42694 - @Override - public void configureFlutterEngine(FlutterEngine flutterEngine) { - flutterEngine.getPlugins().add(new FirebaseCrashlyticsPlugin()); - flutterEngine.getPlugins().add(new E2EPlugin()); - } -} diff --git a/packages/firebase_crashlytics/example/lib/main.dart b/packages/firebase_crashlytics/example/lib/main.dart deleted file mode 100644 index 5f2a9d04a689..000000000000 --- a/packages/firebase_crashlytics/example/lib/main.dart +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; -import 'package:flutter/material.dart'; - -void main() { - // Set `enableInDevMode` 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 for everyday - // development. - Crashlytics.instance.enableInDevMode = true; - - // Pass all uncaught errors to Crashlytics. - FlutterError.onError = Crashlytics.instance.recordFlutterError; - - runZoned(() { - runApp(MyApp()); - }, onError: Crashlytics.instance.recordError); -} - -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); -} - -class _MyAppState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Crashlytics example app'), - ), - body: Center( - child: Column( - children: [ - FlatButton( - child: const Text('Key'), - onPressed: () { - Crashlytics.instance.setString('foo', 'bar'); - }), - FlatButton( - child: const Text('Log'), - onPressed: () { - Crashlytics.instance.log('baz'); - }), - FlatButton( - child: const Text('Crash'), - onPressed: () { - // Use Crashlytics to throw an error. Use this for - // confirmation that errors are being correctly reported. - Crashlytics.instance.crash(); - }), - FlatButton( - child: const Text('Throw Error'), - onPressed: () { - // Example of thrown error, it will be caught and sent to - // 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.delayed(const Duration(seconds: 2), () { - final List list = []; - print(list[100]); - }); - }), - FlatButton( - child: const Text('Record Error'), - onPressed: () { - try { - throw 'error_example'; - } catch (e, s) { - // "context" will append the word "thrown" in the - // Crashlytics console. - Crashlytics.instance - .recordError(e, s, context: 'as an example'); - } - }), - ], - ), - ), - ), - ); - } -} diff --git a/packages/firebase_crashlytics/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/firebase_crashlytics/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 1b4a12d1e91e..000000000000 --- a/packages/firebase_crashlytics/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import firebase_crashlytics - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseCrashlyticsPlugin")) -} diff --git a/packages/firebase_crashlytics/example/pubspec.yaml b/packages/firebase_crashlytics/example/pubspec.yaml deleted file mode 100644 index 92bc95a2036d..000000000000 --- a/packages/firebase_crashlytics/example/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: firebase_crashlytics_example -description: Demonstrates how to use the firebase_crashlytics plugin. - -environment: - sdk: ">=2.0.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - cupertino_icons: ^0.1.2 - - firebase_crashlytics: - path: ../ - -dev_dependencies: - pedantic: ^1.8.0 - e2e: ^0.6.1 - flutter_test: - sdk: flutter - flutter_driver: - sdk: flutter - -flutter: - uses-material-design: true diff --git a/packages/firebase_crashlytics/example/test_driver/crashlytics.dart b/packages/firebase_crashlytics/example/test_driver/crashlytics.dart deleted file mode 100644 index 0169c1e736f4..000000000000 --- a/packages/firebase_crashlytics/example/test_driver/crashlytics.dart +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/widgets.dart'; -import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; - -void main() { - final Completer allTestsCompleter = Completer(); - enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future); - tearDownAll(() => allTestsCompleter.complete(null)); - - 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'); - await crashlytics.recordFlutterError(FlutterErrorDetails( - exception: 'testing', - stack: StackTrace.fromString(''), - context: DiagnosticsNode.message('during testing'), - informationCollector: () => [ - DiagnosticsNode.message('testing'), - DiagnosticsNode.message('information'), - ])); - await crashlytics.recordError( - 'exception_text', - StackTrace.fromString('during testing'), - context: 'during testing example', - ); - }); -} diff --git a/packages/firebase_crashlytics/example/test_driver/crashlytics_test.dart b/packages/firebase_crashlytics/example/test_driver/crashlytics_test.dart deleted file mode 100644 index b0d3305cd652..000000000000 --- a/packages/firebase_crashlytics/example/test_driver/crashlytics_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2019, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter_driver/flutter_driver.dart'; - -Future main() async { - final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); -} diff --git a/packages/firebase_crashlytics/CHANGELOG.md b/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md similarity index 52% rename from packages/firebase_crashlytics/CHANGELOG.md rename to packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md index b3035bb55fb9..f464818e1213 100644 --- a/packages/firebase_crashlytics/CHANGELOG.md +++ b/packages/firebase_crashlytics/firebase_crashlytics/CHANGELOG.md @@ -1,3 +1,66 @@ +## 0.2.0-dev.5 + +* Bump to update changelog. +* See the version `0.2.0-dev.1` changelog for accumulated changes in this dev release. + +## 0.2.0-dev.4 + +* Fixed an error when hot restarting on iOS/macOS (#3432). +* See the version `0.2.0-dev.1` changelog for accumulated changes in this dev release. + +## 0.2.0-dev.3 + +* Fixed an issue with iOS logs appearing as `null` on the Firebase Console. +* Ensure cosmetic Android exception classes keep their name when app obfuscated. +* Fixed an error when hot restarting on Android (#3432). +* See the version `0.2.0-dev.1` changelog for accumulated changes in this dev release. + +## 0.2.0-dev.2 + +* Fixed an iOS variable naming collision that could cause builds to fail when used alongside `firebase_auth`. +* See the version `0.2.0-dev.1` changelog for accumulated changes in this dev release. + +## 0.2.0-dev.1 + +For help migrating to this release please see the [migration guide](https://firebase.flutter.dev/docs/migration). + +* **BREAKING**: Removal of Fabric SDKs and migration to the new Firebase Crashlytics SDK. +* **BREAKING**: The following methods have been removed as they are no longer available on the Firebase Crashlytics SDK: + * `setUserEmail` + * `setUserName` + * `getVersion` + * `isDebuggable` +* **BREAKING**: `log` now returns a Future. Calling `log` now sends logs immediately to the underlying Crashlytics SDK instead of pooling them in Dart. +* **BREAKING**: the methods `setInt`, `setDouble`, `setString` and `setBool` have been replaced by `setCustomKey`. + * `setCustomKey` returns a Future. Calling `setCustomKey` now sends custom keys immediately to the underlying Crashlytics SDK instead of pooling them in Dart. +* **DEPRECATED**: `enableInDevMode` has been deprecated, use `isCrashlyticsCollectionEnabled` and `setCrashlyticsCollectionEnabled` instead. +* **DEPRECATED**: `Crashlytics` has been deprecated, use `FirebaseCrashlytics` instead. +* **NEW**: Custom keys that are automatically added by FlutterFire when calling `reportError` are now prefixed with `flutter_error_`. +* **NEW**: Calling `.crash()` on Android & iOS/macOS now reports a custom named exception to the Firebase Console. This allows you to easily locate test crashes. + * Name: `FirebaseCrashlyticsTestCrash`. + * Message: `This is a test crash caused by calling .crash() in Dart.`. +* **NEW**: `recordError` now uses a named native exception when reporting to the Firebase Console. This allows you to easily locate errors originating from Flutter. + * Name: `FlutterError`. +* **NEW**: Added support for `checkForUnsentReports`. + * Checks a device for any fatal or non-fatal crash reports that haven't yet been sent to Crashlytics. + * See reference API docs for more information. +* **NEW**: Added support for `deleteUnsentReports`. + * If automatic data collection is disabled, this method queues up all the reports on a device for deletion. + * See reference API docs for more information. +* **NEW**: Added support for `didCrashOnPreviousExecution`. + * Checks whether the app crashed on its previous run. + * See reference API docs for more information. +* **NEW**: Added support for `sendUnsentReports`. + * If automatic data collection is disabled, this method queues up all the reports on a device to send to Crashlytics. + * See reference API docs for more information. +* **NEW**: Added support for `setCrashlyticsCollectionEnabled`. + * Enables/disables automatic data collection by Crashlytics. + * See reference API docs for more information. +* **NEW**: Added support for `isCrashlyticsCollectionEnabled`. + * Whether the current Crashlytics instance is collecting reports. If false, then no crash reporting data is sent to Firebase. + * See reference API docs for more information. +* **FIX**: Fixed 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/LICENSE b/packages/firebase_crashlytics/firebase_crashlytics/LICENSE similarity index 100% rename from packages/firebase_crashlytics/LICENSE rename to packages/firebase_crashlytics/firebase_crashlytics/LICENSE diff --git a/packages/firebase_crashlytics/firebase_crashlytics/README.md b/packages/firebase_crashlytics/firebase_crashlytics/README.md new file mode 100644 index 000000000000..1f859a2c4fd8 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/README.md @@ -0,0 +1,26 @@ +# Firebase Crashlytics for Flutter + +[![pub package](https://img.shields.io/pub/v/firebase_crashlytics.svg)](https://pub.dev/packages/firebase_crashlytics) + +A Flutter plugin to use the [Firebase Crashlytics API](https://firebase.google.com/products/crashlytics/). + +To learn more about Firebase, please visit the [Firebase website](https://firebase.google.com) + +## Getting Started + +To get started with FlutterFire, please [see the documentation](https://firebase.flutter.dev/docs/overview) +available at [https://firebase.flutter.dev](https://firebase.flutter.dev/docs/overview) + +## Usage + +To use this plugin, please visit the [Crashlytics Usage documentation](https://firebase.flutter.dev/docs/crashlytics/overview) + +## Issues and feedback + +Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). + +Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). + +To contribute a change to this plugin, +please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) +and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_crashlytics/android/.gitignore b/packages/firebase_crashlytics/firebase_crashlytics/android/.gitignore similarity index 100% rename from packages/firebase_crashlytics/android/.gitignore rename to packages/firebase_crashlytics/firebase_crashlytics/android/.gitignore diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle b/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle new file mode 100644 index 000000000000..4f69f13400dd --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle @@ -0,0 +1,60 @@ +group 'io.flutter.plugins.firebase.crashlytics' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +def firebaseCoreProject = findProject(':firebase_core') +if (firebaseCoreProject == null) { + throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?') +} else if (!firebaseCoreProject.properties['FirebaseSDKVersion']) { + throw new GradleException('A newer version of the firebase_core FlutterFire plugin is required, please update your firebase_core pubspec dependency.') +} + +def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { + if (!rootProject.ext.has('FlutterFire')) return firebaseCoreProject.properties[name] + if (!rootProject.ext.get('FlutterFire')[name]) return firebaseCoreProject.properties[name] + return rootProject.ext.get('FlutterFire').get(name) +} + +android { + compileSdkVersion 28 + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") + implementation 'com.google.firebase:firebase-crashlytics' + implementation 'androidx.annotation:annotation:1.1.0' +} + +apply from: file("./user-agent.gradle") + diff --git a/packages/firebase_crashlytics/android/gradle.properties b/packages/firebase_crashlytics/firebase_crashlytics/android/gradle.properties similarity index 100% rename from packages/firebase_crashlytics/android/gradle.properties rename to packages/firebase_crashlytics/firebase_crashlytics/android/gradle.properties diff --git a/packages/firebase_crashlytics/android/settings.gradle b/packages/firebase_crashlytics/firebase_crashlytics/android/settings.gradle similarity index 100% rename from packages/firebase_crashlytics/android/settings.gradle rename to packages/firebase_crashlytics/firebase_crashlytics/android/settings.gradle diff --git a/packages/firebase_crashlytics/android/src/main/AndroidManifest.xml b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/AndroidManifest.xml similarity index 69% rename from packages/firebase_crashlytics/android/src/main/AndroidManifest.xml rename to packages/firebase_crashlytics/firebase_crashlytics/android/src/main/AndroidManifest.xml index 535d778d3920..2feb406d9bae 100644 --- a/packages/firebase_crashlytics/android/src/main/AndroidManifest.xml +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + package="io.flutter.plugins.firebase.crashlytics"> - diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/Constants.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/Constants.java new file mode 100644 index 000000000000..dda4dae7127f --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/Constants.java @@ -0,0 +1,22 @@ +package io.flutter.plugins.firebase.crashlytics; + +public class Constants { + public static final String EXCEPTION = "exception"; + public static final String CONTEXT = "context"; + public static final String INFORMATION = "information"; + public static final String STACK_TRACE_ELEMENTS = "stackTraceElements"; + public static final String FLUTTER_ERROR_EXCEPTION = "flutter_error_exception"; + public static final String FLUTTER_ERROR_REASON = "flutter_error_reason"; + public static final String MESSAGE = "message"; + public static final String ENABLED = "enabled"; + public static final String IDENTIFIER = "identifier"; + public static final String KEY = "key"; + public static final String VALUE = "value"; + public static final String FILE = "file"; + public static final String LINE = "line"; + public static final String CLASS = "class"; + public static final String METHOD = "method"; + public static final String DID_CRASH_ON_PREVIOUS_EXECUTION = "didCrashOnPreviousExecution"; + public static final String UNSENT_REPORTS = "unsentReports"; + public static final String IS_CRASHLYTICS_COLLECTION_ENABLED = "isCrashlyticsCollectionEnabled"; +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.java new file mode 100644 index 000000000000..e27311499d27 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.java @@ -0,0 +1,20 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.crashlytics; + +import androidx.annotation.Keep; + +/** + * This class is purely cosmetic - to indicate on the Crashlytics console that it's a + * FirebaseCrashlyticsTestCrash error rather than the generic `java.lang.RuntimeException`. + * + *

Name and message match iOS implementation. + */ +@Keep +public class FirebaseCrashlyticsTestCrash extends RuntimeException { + FirebaseCrashlyticsTestCrash() { + super("This is a test crash caused by calling .crash() in Dart."); + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterError.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterError.java new file mode 100644 index 000000000000..50e05c5e8e80 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterError.java @@ -0,0 +1,20 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.crashlytics; + +import androidx.annotation.Keep; + +/** + * This class is purely cosmetic - to indicate on the Crashlytics console that it's a FlutterError + * error rather than the generic `java.lang.Exception`. + * + *

Name matches iOS implementation. + */ +@Keep +public class FlutterError extends Exception { + FlutterError(String message) { + super(message); + } +} diff --git a/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FlutterFirebaseAppRegistrar.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java similarity index 91% rename from packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FlutterFirebaseAppRegistrar.java rename to packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java index ee58745a5430..0f0d33a39a83 100644 --- a/packages/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/firebasecrashlytics/FlutterFirebaseAppRegistrar.java +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package io.flutter.plugins.firebase.crashlytics.firebasecrashlytics; +package io.flutter.plugins.firebase.crashlytics; import androidx.annotation.Keep; import com.google.firebase.components.Component; diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.java new file mode 100644 index 000000000000..3d479e1c3f6d --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.java @@ -0,0 +1,336 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.crashlytics; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Handler; +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; +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; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; +import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** FlutterFirebaseCrashlyticsPlugin */ +public class FlutterFirebaseCrashlyticsPlugin + implements FlutterFirebasePlugin, FlutterPlugin, MethodCallHandler { + public static final String TAG = "FLTFirebaseCrashlytics"; + private MethodChannel channel; + + /** Plugin registration. */ + public static void registerWith(Registrar registrar) { + FlutterFirebaseCrashlyticsPlugin instance = new FlutterFirebaseCrashlyticsPlugin(); + instance.initInstance(registrar.messenger()); + } + + private void initInstance(BinaryMessenger messenger) { + String channelName = "plugins.flutter.io/firebase_crashlytics"; + channel = new MethodChannel(messenger, channelName); + channel.setMethodCallHandler(this); + FlutterFirebasePluginRegistry.registerPlugin(channelName, this); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + initInstance(binding.getBinaryMessenger()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + if (channel != null) { + channel.setMethodCallHandler(null); + channel = null; + } + } + + private Task> checkForUnsentReports() { + return Tasks.call( + cachedThreadPool, + () -> { + final boolean unsentReports = + Tasks.await(FirebaseCrashlytics.getInstance().checkForUnsentReports()); + + return new HashMap() { + { + put(Constants.UNSENT_REPORTS, unsentReports); + } + }; + }); + } + + private void crash() { + new Handler() + .postDelayed( + () -> { + throw new FirebaseCrashlyticsTestCrash(); + }, + 50); + } + + private Task deleteUnsentReports() { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseCrashlytics.getInstance().deleteUnsentReports(); + return null; + }); + } + + private Task> didCrashOnPreviousExecution() { + return Tasks.call( + cachedThreadPool, + () -> { + final boolean didCrashOnPreviousExecution = + FirebaseCrashlytics.getInstance().didCrashOnPreviousExecution(); + + return new HashMap() { + { + put(Constants.DID_CRASH_ON_PREVIOUS_EXECUTION, didCrashOnPreviousExecution); + } + }; + }); + } + + private Task recordError(final Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); + + final String dartExceptionMessage = + (String) Objects.requireNonNull(arguments.get(Constants.EXCEPTION)); + final String context = (String) arguments.get(Constants.CONTEXT); + final String information = + (String) Objects.requireNonNull(arguments.get(Constants.INFORMATION)); + + Exception exception; + if (context != null) { + // Set a "reason" (to match iOS) to show where the exception was thrown. + crashlytics.setCustomKey(Constants.FLUTTER_ERROR_REASON, "thrown " + context); + exception = + new FlutterError(dartExceptionMessage + ". " + "Error thrown " + context + "."); + } else { + exception = new FlutterError(dartExceptionMessage); + } + crashlytics.setCustomKey(Constants.FLUTTER_ERROR_EXCEPTION, dartExceptionMessage); + + final List elements = new ArrayList<>(); + @SuppressWarnings("unchecked") + final List> errorElements = + (List>) + Objects.requireNonNull(arguments.get(Constants.STACK_TRACE_ELEMENTS)); + + for (Map errorElement : errorElements) { + final StackTraceElement stackTraceElement = generateStackTraceElement(errorElement); + if (stackTraceElement != null) { + elements.add(stackTraceElement); + } + } + exception.setStackTrace(elements.toArray(new StackTraceElement[0])); + + // Log information. + if (!information.isEmpty()) { + crashlytics.log(information); + } + + crashlytics.recordException(exception); + return null; + }); + } + + private Task log(final Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + String message = (String) Objects.requireNonNull(arguments.get(Constants.MESSAGE)); + FirebaseCrashlytics.getInstance().log(message); + return null; + }); + } + + private Task sendUnsentReports() { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseCrashlytics.getInstance().sendUnsentReports(); + return null; + }); + } + + private Task> setCrashlyticsCollectionEnabled( + final Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + Boolean enabled = (Boolean) Objects.requireNonNull(arguments.get(Constants.ENABLED)); + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled); + return new HashMap() { + { + put( + Constants.IS_CRASHLYTICS_COLLECTION_ENABLED, + isCrashlyticsCollectionEnabled(FirebaseApp.getInstance())); + } + }; + }); + } + + private Task setUserIdentifier(final Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + String identifier = (String) Objects.requireNonNull(arguments.get(Constants.IDENTIFIER)); + FirebaseCrashlytics.getInstance().setUserId(identifier); + return null; + }); + } + + private Task setCustomKey(final Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + String key = (String) Objects.requireNonNull(arguments.get(Constants.KEY)); + String value = (String) Objects.requireNonNull(arguments.get(Constants.VALUE)); + FirebaseCrashlytics.getInstance().setCustomKey(key, value); + return null; + }); + } + + @Override + public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result result) { + Task methodCallTask; + + switch (call.method) { + case "Crashlytics#checkForUnsentReports": + methodCallTask = checkForUnsentReports(); + break; + case "Crashlytics#crash": + crash(); + return; + case "Crashlytics#deleteUnsentReports": + methodCallTask = deleteUnsentReports(); + break; + case "Crashlytics#didCrashOnPreviousExecution": + methodCallTask = didCrashOnPreviousExecution(); + break; + case "Crashlytics#recordError": + methodCallTask = recordError(call.arguments()); + break; + case "Crashlytics#log": + methodCallTask = log(call.arguments()); + break; + case "Crashlytics#sendUnsentReports": + methodCallTask = sendUnsentReports(); + break; + case "Crashlytics#setCrashlyticsCollectionEnabled": + methodCallTask = setCrashlyticsCollectionEnabled(call.arguments()); + break; + case "Crashlytics#setUserIdentifier": + methodCallTask = setUserIdentifier(call.arguments()); + break; + case "Crashlytics#setCustomKey": + methodCallTask = setCustomKey(call.arguments()); + break; + default: + result.notImplemented(); + return; + } + + methodCallTask.addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + result.success(task.getResult()); + } else { + Exception exception = task.getException(); + String message = + exception != null ? exception.getMessage() : "An unknown error occurred"; + result.error("firebase_crashlytics", message, null); + } + }); + } + + /** + * Extract StackTraceElement from Dart stack trace element. + * + * @param errorElement Map representing the parts of a Dart error. + * @return Stack trace element to be used as part of an Exception stack trace. + */ + private StackTraceElement generateStackTraceElement(Map errorElement) { + try { + String fileName = errorElement.get(Constants.FILE); + String lineNumber = errorElement.get(Constants.LINE); + String className = errorElement.get(Constants.CLASS); + String methodName = errorElement.get(Constants.METHOD); + + return new StackTraceElement( + className == null ? "" : className, + methodName, + fileName, + Integer.parseInt(Objects.requireNonNull(lineNumber))); + } catch (Exception e) { + Log.e(TAG, "Unable to generate stack trace element from Dart error."); + return null; + } + } + + private SharedPreferences getCrashlyticsSharedPrefs(Context context) { + return context.getSharedPreferences("com.google.firebase.crashlytics", 0); + } + + // TODO remove once Crashlytics public API supports isCrashlyticsCollectionEnabled + /** + * Firebase Crashlytics SDK doesn't provide a way to read current enabled status. So we read it + * ourselves from shared preferences instead. + */ + private boolean isCrashlyticsCollectionEnabled(FirebaseApp app) { + boolean enabled; + SharedPreferences crashlyticsSharedPrefs = + getCrashlyticsSharedPrefs(app.getApplicationContext()); + + if (crashlyticsSharedPrefs.contains("firebase_crashlytics_collection_enabled")) { + enabled = crashlyticsSharedPrefs.getBoolean("firebase_crashlytics_collection_enabled", true); + } else { + if (app.isDataCollectionDefaultEnabled()) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true); + enabled = true; + } else { + enabled = false; + } + } + + return enabled; + } + + @Override + public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { + return Tasks.call( + () -> + new HashMap() { + { + put( + Constants.IS_CRASHLYTICS_COLLECTION_ENABLED, + isCrashlyticsCollectionEnabled(FirebaseApp.getInstance())); + } + }); + } + + @Override + public Task didReinitializeFirebaseCore() { + return Tasks.call(() -> null); + } +} diff --git a/packages/firebase_crashlytics/android/user-agent.gradle b/packages/firebase_crashlytics/firebase_crashlytics/android/user-agent.gradle similarity index 100% rename from packages/firebase_crashlytics/android/user-agent.gradle rename to packages/firebase_crashlytics/firebase_crashlytics/android/user-agent.gradle diff --git a/packages/firebase_crashlytics/example/.gitignore b/packages/firebase_crashlytics/firebase_crashlytics/example/.gitignore similarity index 100% rename from packages/firebase_crashlytics/example/.gitignore rename to packages/firebase_crashlytics/firebase_crashlytics/example/.gitignore diff --git a/packages/firebase_crashlytics/example/.metadata b/packages/firebase_crashlytics/firebase_crashlytics/example/.metadata similarity index 100% rename from packages/firebase_crashlytics/example/.metadata rename to packages/firebase_crashlytics/firebase_crashlytics/example/.metadata diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/README.md b/packages/firebase_crashlytics/firebase_crashlytics/example/README.md new file mode 100644 index 000000000000..6a8e6ab1fda1 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/README.md @@ -0,0 +1,8 @@ +# firebase_crashlytics_example + +Demonstrates how to use the firebase_crashlytics plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/firebase_crashlytics/example/android/app/build.gradle b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/build.gradle similarity index 77% rename from packages/firebase_crashlytics/example/android/app/build.gradle rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/build.gradle index 891334951d8d..34cdb780ed07 100644 --- a/packages/firebase_crashlytics/example/android/app/build.gradle +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/build.gradle @@ -32,10 +32,9 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample" + applicationId 'io.flutter.plugins.firebasecrashlyticsexample' minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -55,14 +54,11 @@ flutter { } dependencies { + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - implementation "android.arch.lifecycle:runtime:1.1.1" - implementation "android.arch.lifecycle:common:1.1.1" - implementation "android.arch.lifecycle:common-java8:1.1.1" - 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/firebase_crashlytics/example/android/app/google-services.json b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/google-services.json new file mode 100644 index 000000000000..d7215ee2004b --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/google-services.json @@ -0,0 +1,531 @@ +{ + "project_info": { + "project_number": "448618578101", + "firebase_url": "https://react-native-firebase-testing.firebaseio.com", + "project_id": "react-native-firebase-testing", + "storage_bucket": "react-native-firebase-testing.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:e708991417cc7542ac3efc", + "android_client_info": { + "package_name": "com.example.testapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:cc6c1dc7a65cc83c", + "android_client_info": { + "package_name": "com.invertase.testing" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-h0o9b94jnhcoal2qgjn7s7ckkc2n7okq.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-gva3jv7cr8qquj04k0o7cni674j65kha.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "448618578101-a9p7bj5jlakabp22fo3cbkj7nsmag24e.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "889b4292c735f371168a372cc7778992cd8a5052" + } + }, + { + "client_id": "448618578101-pdjje2lkv3p941e03hkrhfa7459cr2v8.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "992e468b990cc418f306d0131be61ecfad800ac1" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:cc5ce91648e65dbeac3efc", + "android_client_info": { + "package_name": "com.notifeetestapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-j9nluebtat700ua550esfvaf64gbo5l5.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.notifeetestapp", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:6aa085e64d694703ac3efc", + "android_client_info": { + "package_name": "com.rnfbdemo" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-a8tk83htomfvrelhu71j93safubvipdj.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.rnfbdemo", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:c017beb5f26b66fcac3efc", + "android_client_info": { + "package_name": "com.testapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-q1ffje1ttluf8vfhi4aa50pag231q9dt.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.testapp", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:6bf0706c1f27114dac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.cloudfunctions.cloudfunctionsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:5cc04a661d27c405ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:3ad281c0067ccf97ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.firestoreexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:9d44a7b85d1ab0baac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebaseauthexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-velutq65ok2dr5ohh0oi1q62irr920ss.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "29142b8612b4b6a0ba0fefd1dbf65ab565fb2cbd" + } + }, + { + "client_id": "448618578101-2brn4509s8513kjf9a0i6kvtb9qnght5.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "e1604565b51994d10886de1d91da9968dfec02ed" + } + }, + { + "client_id": "448618578101-2kqet2itdu42g0avs2v3fbsmtf6a45gg.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "939efbe8eaa5aaf50396b19fe980ef4a8df1c6e7" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:29bf96f913c195f5ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasecrashlyticsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:553625b1be8cf2efac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasestorageexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:b90da4e868ff1aafac3efc", + "android_client_info": { + "package_name": "org.reactjs.native.example.rnfbTestApplication" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-fft7llalbhpetiiuqg4uk19jfp0tfefe.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "org.reactjs.native.example.rnfbTestApplication", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..e3407c745b1a --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebasecrashlyticsexample/EmbeddingV1Activity.java b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebasecrashlyticsexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..1c68751e1cfc --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/java/io/flutter/plugins/firebasecrashlyticsexample/EmbeddingV1Activity.java @@ -0,0 +1,19 @@ +package io.flutter.plugins.firebasecrashlyticsexample; + +import android.os.Bundle; +import dev.flutter.plugins.e2e.E2EPlugin; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin; +import io.flutter.plugins.firebase.crashlytics.FlutterFirebaseCrashlyticsPlugin; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FlutterFirebaseCorePlugin.registerWith( + registrarFor("io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin")); + FlutterFirebaseCrashlyticsPlugin.registerWith( + registrarFor("io.flutter.plugins.firebase.crashlytics.FlutterFirebaseCrashlyticsPlugin")); + E2EPlugin.registerWith(registrarFor("dev.flutter.plugins.e2e.E2EPlugin")); + } +} diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/drawable/launch_background.xml b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/firebase_crashlytics/example/android/app/src/main/res/values/styles.xml b/packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/firebase_crashlytics/example/android/app/src/main/res/values/styles.xml rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/app/src/main/res/values/styles.xml diff --git a/packages/firebase_crashlytics/example/android/build.gradle b/packages/firebase_crashlytics/firebase_crashlytics/example/android/build.gradle similarity index 63% rename from packages/firebase_crashlytics/example/android/build.gradle rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/build.gradle index faeab902b866..8d405d7f715c 100644 --- a/packages/firebase_crashlytics/example/android/build.gradle +++ b/packages/firebase_crashlytics/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.android.tools.build:gradle:3.5.0' + classpath 'com.google.gms:google-services:4.3.3' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' } } diff --git a/packages/firebase_crashlytics/example/android/gradle.properties b/packages/firebase_crashlytics/firebase_crashlytics/example/android/gradle.properties similarity index 100% rename from packages/firebase_crashlytics/example/android/gradle.properties rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/gradle.properties diff --git a/packages/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_crashlytics/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 92% rename from packages/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties index 2819f022f1fd..296b146b7318 100644 --- a/packages/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/firebase_crashlytics/example/android/settings.gradle b/packages/firebase_crashlytics/firebase_crashlytics/example/android/settings.gradle similarity index 100% rename from packages/firebase_crashlytics/example/android/settings.gradle rename to packages/firebase_crashlytics/firebase_crashlytics/example/android/settings.gradle diff --git a/packages/firebase_crashlytics/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/firebase_crashlytics/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/firebase_crashlytics/example/ios/Flutter/Debug.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/ios/Flutter/Debug.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/Debug.xcconfig diff --git a/packages/firebase_crashlytics/example/ios/Flutter/Release.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/ios/Flutter/Release.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Flutter/Release.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner/GoogleService-Info.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/GoogleService-Info.plist similarity index 54% rename from packages/firebase_crashlytics/example/macos/Runner/GoogleService-Info.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/GoogleService-Info.plist index c9c6c3ae12aa..e5559131439f 100644 --- a/packages/firebase_crashlytics/example/macos/Runner/GoogleService-Info.plist +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/GoogleService-Info.plist @@ -2,39 +2,37 @@ - AD_UNIT_ID_FOR_BANNER_TEST - ca-app-pub-3940256099942544/2934735716 - AD_UNIT_ID_FOR_INTERSTITIAL_TEST - ca-app-pub-3940256099942544/4411468910 CLIENT_ID - 380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un.apps.googleusercontent.com + 448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn.apps.googleusercontent.com REVERSED_CLIENT_ID - com.googleusercontent.apps.380450695418-j9bquuvdhv7iimnca4n7mhrp9a0kv9un + com.googleusercontent.apps.448618578101-54jhd806d0tr4vkgode0b4fi8iruvjpn + ANDROID_CLIENT_ID + 448618578101-velutq65ok2dr5ohh0oi1q62irr920ss.apps.googleusercontent.com API_KEY - AIzaSyDKzrI_W_MaUt46jthsPB7FTG-RdSKeKEw + AIzaSyAHAsf51D0A407EklG1bs-5wA7EbyfNFg0 GCM_SENDER_ID - 380450695418 + 448618578101 PLIST_VERSION 1 BUNDLE_ID io.flutter.plugins.firebase.crashlytics.firebaseCrashlyticsExample PROJECT_ID - firebase-for-flutter-xry + react-native-firebase-testing STORAGE_BUCKET - firebase-for-flutter-xry.appspot.com + react-native-firebase-testing.appspot.com IS_ADS_ENABLED - + IS_ANALYTICS_ENABLED IS_APPINVITE_ENABLED - + IS_GCM_ENABLED IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:380450695418:ios:2693ad5ab6a23cde + 1:448618578101:ios:3d7b3d90894e689eac3efc DATABASE_URL - https://fir-for-flutter-xry.firebaseio.com + https://react-native-firebase-testing.firebaseio.com \ No newline at end of file diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj similarity index 98% rename from packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj index b616cae76e0c..39a4ea15c073 100644 --- a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/project.pbxproj @@ -184,6 +184,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -240,7 +241,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$(SRCROOT)/newInputFile", + "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); outputFileListPaths = ( ); @@ -248,7 +249,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; @@ -337,7 +338,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -409,7 +409,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -463,7 +462,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_crashlytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/AppDelegate.h rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.h diff --git a/packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/AppDelegate.m rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/AppDelegate.m diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/firebase_crashlytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/firebase_crashlytics/example/ios/Runner/Base.lproj/Main.storyboard b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/firebase_crashlytics/example/ios/Runner/Info.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/Info.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/Info.plist diff --git a/packages/firebase_crashlytics/example/ios/Runner/main.m b/packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/main.m similarity index 100% rename from packages/firebase_crashlytics/example/ios/Runner/main.m rename to packages/firebase_crashlytics/firebase_crashlytics/example/ios/Runner/main.m diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/lib/main.dart b/packages/firebase_crashlytics/firebase_crashlytics/example/lib/main.dart new file mode 100644 index 000000000000..7e30509f67eb --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/lib/main.dart @@ -0,0 +1,203 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +// Toggle this to cause an async error to be thrown during initialization +// and to test that runZonedGuarded() catches the error +final _kShouldTestAsyncErrorOnInit = false; + +// Toggle this for testing Crashlytics in your app locally. +final _kTestingCrashlytics = true; + +main() { + WidgetsFlutterBinding.ensureInitialized(); + runZonedGuarded(() { + runApp(MyApp()); + }, (error, stackTrace) { + print('runZonedGuarded: Caught error in my root zone.'); + FirebaseCrashlytics.instance.recordError(error, stackTrace); + }); +} + +// ignore: public_member_api_docs +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + Future _initializeFlutterFireFuture; + + Future _testAsyncErrorOnInit() async { + Future.delayed(const Duration(seconds: 2), () { + final List list = []; + print(list[100]); + }); + } + + // Define an async function to initialize FlutterFire + Future _initializeFlutterFire() async { + // Wait for Firebase to initialize + await Firebase.initializeApp(); + + if (_kTestingCrashlytics) { + // Force enable crashlytics collection enabled if we're testing it. + await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); + } else { + // Else only enable it in non-debug builds. + // You could additionally extend this to allow users to opt-in. + await FirebaseCrashlytics.instance + .setCrashlyticsCollectionEnabled(!kDebugMode); + } + + // Pass all uncaught errors to Crashlytics. + Function originalOnError = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails errorDetails) async { + await FirebaseCrashlytics.instance.recordFlutterError(errorDetails); + // Forward to original handler. + originalOnError(errorDetails); + }; + + if (_kShouldTestAsyncErrorOnInit) { + await _testAsyncErrorOnInit(); + } + } + + @override + void initState() { + super.initState(); + _initializeFlutterFireFuture = _initializeFlutterFire(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Crashlytics example app'), + ), + body: FutureBuilder( + future: _initializeFlutterFireFuture, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + if (snapshot.hasError) { + return Center( + child: Text('Error: ${snapshot.error}'), + ); + } + return Center( + child: Column( + children: [ + RaisedButton( + child: const Text('Key'), + onPressed: () { + FirebaseCrashlytics.instance + .setCustomKey('example', 'flutterfire'); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + 'Custom Key "example: flutterfire" has been set \n' + 'Key will appear in Firebase Console once app has crashed and reopened'), + duration: Duration(seconds: 5), + )); + }), + RaisedButton( + child: const Text('Log'), + onPressed: () { + FirebaseCrashlytics.instance + .log('This is a log example'); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + 'The message "This is a log example" has been logged \n' + 'Message will appear in Firebase Console once app has crashed and reopened'), + duration: Duration(seconds: 5), + )); + }), + RaisedButton( + child: const Text('Crash'), + onPressed: () async { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('App will crash is 5 seconds \n' + 'Please reopen to send data to Crashlytics'), + duration: Duration(seconds: 5), + )); + + // Delay crash for 5 seconds + sleep(const Duration(seconds: 5)); + + // Use FirebaseCrashlytics to throw an error. Use this for + // confirmation that errors are being correctly reported. + FirebaseCrashlytics.instance.crash(); + }), + RaisedButton( + child: const Text('Throw Error'), + onPressed: () { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Thrown error has been caught \n' + 'Please crash and reopen to send data to Crashlytics'), + duration: Duration(seconds: 5), + )); + + // Example of thrown error, it will be caught and sent to + // Crashlytics. + throw StateError('Uncaught error thrown by app'); + }), + RaisedButton( + child: const Text('Async out of bounds'), + onPressed: () { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + 'Uncaught Exception that is handled by second parameter of runZonedGuarded \n' + 'Please crash and reopen to send data to Crashlytics'), + duration: Duration(seconds: 5), + )); + + // Example of an exception that does not get caught + // by `FlutterError.onError` but is caught by + // `runZonedGuarded`. + runZonedGuarded(() { + Future.delayed(const Duration(seconds: 2), + () { + final List list = []; + print(list[100]); + }); + }, FirebaseCrashlytics.instance.recordError); + }), + RaisedButton( + child: const Text('Record Error'), + onPressed: () async { + try { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Recorded Error \n' + 'Please crash and reopen to send data to Crashlytics'), + duration: Duration(seconds: 5), + )); + throw 'error_example'; + } catch (e, s) { + // "context" will append the word "thrown" in the + // Crashlytics console. + await FirebaseCrashlytics.instance + .recordError(e, s, context: 'as an example'); + } + }), + ], + ), + ); + break; + default: + return Center(child: Text('Loading')); + } + }, + ), + ), + ); + } +} diff --git a/packages/firebase_crashlytics/example/macos/.gitignore b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/.gitignore similarity index 100% rename from packages/firebase_crashlytics/example/macos/.gitignore rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/.gitignore diff --git a/packages/firebase_crashlytics/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Flutter/Flutter-Release.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_crashlytics/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/firebase_crashlytics/example/macos/Runner/AppDelegate.swift b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/AppDelegate.swift rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/AppDelegate.swift diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/firebase_crashlytics/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/firebase_crashlytics/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner/Configs/Debug.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Configs/Debug.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner/Configs/Release.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Configs/Release.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner/Configs/Warnings.xcconfig b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/firebase_crashlytics/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/DebugProfile.entitlements rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/firebase_crashlytics/example/ios/GoogleService-Info.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/GoogleService-Info.plist similarity index 100% rename from packages/firebase_crashlytics/example/ios/GoogleService-Info.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/GoogleService-Info.plist diff --git a/packages/firebase_crashlytics/example/macos/Runner/Info.plist b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Info.plist similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Info.plist rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Info.plist diff --git a/packages/firebase_crashlytics/example/macos/Runner/MainFlutterWindow.swift b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/MainFlutterWindow.swift rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/firebase_crashlytics/example/macos/Runner/Release.entitlements b/packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/firebase_crashlytics/example/macos/Runner/Release.entitlements rename to packages/firebase_crashlytics/firebase_crashlytics/example/macos/Runner/Release.entitlements diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml new file mode 100644 index 000000000000..09230c9c896b --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/pubspec.yaml @@ -0,0 +1,34 @@ +name: firebase_crashlytics_example +description: Demonstrates how to use the firebase_crashlytics plugin. +author: Flutter Team + +dependencies: + flutter: + sdk: flutter + firebase_crashlytics: + path: ../ + firebase_core: + path: ../../../firebase_core/firebase_core + +dependency_overrides: + firebase_crashlytics_platform_interface: + path: ../../firebase_crashlytics_platform_interface + firebase_crashlytics: + path: ../ + firebase_core: + path: ../../../firebase_core/firebase_core + +dev_dependencies: + pedantic: ^1.8.0 + drive: 0.0.1-6.0.pre + flutter_driver: + sdk: flutter + test: any + e2e: ^0.6.1 + +flutter: + uses-material-design: true + +environment: + sdk: ">=2.8.0 <3.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e.dart b/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e.dart new file mode 100644 index 000000000000..ab5557e9e6c4 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e.dart @@ -0,0 +1,140 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:e2e/e2e.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + group('$FirebaseCrashlytics', () { + FirebaseCrashlytics crashlytics; + + setUpAll(() async { + await Firebase.initializeApp(); + crashlytics = FirebaseCrashlytics.instance; + }); + + group('checkForUnsentReports', () { + test('should throw if automatic crash report is enabled', () async { + await crashlytics.setCrashlyticsCollectionEnabled(true); + try { + await crashlytics.checkForUnsentReports(); + fail("Error did not throw"); + } catch (e) { + print(e); + } + }); + + test('checks device cache for unsent crashlytics reports', () async { + await crashlytics.setCrashlyticsCollectionEnabled(false); + var unsentReports = await crashlytics.checkForUnsentReports(); + + expect(unsentReports, isFalse); + }); + }); + + group('deleteUnsentReports', () { + // This is currently only testing that we can delete reports without crashing. + test('deletes unsent crashlytics reports', () async { + await crashlytics.deleteUnsentReports(); + }); + }); + + group('didCrashOnPreviousExecution', () { + test('checks if app crashed on previous execution', () async { + var didCrash = await crashlytics.didCrashOnPreviousExecution(); + expect(didCrash, isFalse); + }); + }); + + group('recordError', () { + // This is currently only testing that we can log errors without crashing. + test('should log error', () async { + await crashlytics.recordError( + 'foo exception', StackTrace.fromString('during testing')); + }); + + // This is currently only testing that we can log flutter errors without crashing. + test('should record flutter error', () async { + await crashlytics.recordFlutterError(FlutterErrorDetails( + exception: 'foo exception', + stack: StackTrace.fromString(''), + context: DiagnosticsNode.message('bar context'), + informationCollector: () => [ + DiagnosticsNode.message('first message'), + DiagnosticsNode.message('second message') + ])); + }); + }); + + group('log', () { + test('should throw if message is null', () async { + expect(() => crashlytics.log(null), throwsAssertionError); + }); + + // This is currently only testing that we can log without crashing. + test('accepts any value', () async { + await crashlytics.log('flutter'); + }); + }); + + group('sendUnsentReports', () { + // This is currently only testing that we can send unsent reports without crashing. + test('sends unsent reports to crashlytics server', () async { + await crashlytics.sendUnsentReports(); + }); + }); + + group('setCrashlyticsCollectionEnabled', () { + test('should throw if null', () async { + expect(() => crashlytics.setCrashlyticsCollectionEnabled(null), + throwsAssertionError); + }); + + // This is currently only testing that we can send unsent reports without crashing. + test('should update to true', () async { + await crashlytics.setCrashlyticsCollectionEnabled(true); + }); + + // This is currently only testing that we can send unsent reports without crashing. + test('should update to false', () async { + await crashlytics.setCrashlyticsCollectionEnabled(false); + }); + }); + + group('setUserIdentifier', () { + test('should throw if null', () async { + expect(() => crashlytics.setUserIdentifier(null), throwsAssertionError); + }); + + // This is currently only testing that we can log errors without crashing. + test('should update', () async { + await crashlytics.setUserIdentifier('foo'); + }); + }); + + group('setCustomKey', () { + test('should throw if null', () async { + expect( + () => crashlytics.setCustomKey(null, null), throwsAssertionError); + expect( + () => crashlytics.setCustomKey('foo', null), throwsAssertionError); + expect(() => crashlytics.setCustomKey('foo', []), throwsAssertionError); + expect(() => crashlytics.setCustomKey('foo', {}), throwsAssertionError); + }); + + // This is currently only testing that we can log errors without crashing. + test('should update', () async { + await crashlytics.setCustomKey('fooString', 'bar'); + await crashlytics.setCustomKey('fooBool', true); + await crashlytics.setCustomKey('fooInt', 42); + await crashlytics.setCustomKey('fooDouble', 42.0); + }); + }); + }); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e_test.dart b/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e_test.dart new file mode 100644 index 000000000000..27e264e5475b --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/example/test_driver/firebase_crashlytics_e2e_test.dart @@ -0,0 +1,7 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:e2e/e2e_driver.dart' as e2e; + +void main() => e2e.main(); diff --git a/packages/firebase_crashlytics/ios/Assets/.gitkeep b/packages/firebase_crashlytics/firebase_crashlytics/ios/Assets/.gitkeep similarity index 100% rename from packages/firebase_crashlytics/ios/Assets/.gitkeep rename to packages/firebase_crashlytics/firebase_crashlytics/ios/Assets/.gitkeep diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.h b/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.h new file mode 100644 index 000000000000..468b594390c0 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.h @@ -0,0 +1,15 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#import +#import + +@interface FLTFirebaseCrashlyticsPlugin : FLTFirebasePlugin +@end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.m new file mode 100644 index 000000000000..fc3fd201fcc3 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/Classes/FLTFirebaseCrashlyticsPlugin.m @@ -0,0 +1,226 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTFirebaseCrashlyticsPlugin.h" + +#import +#import + +NSString *const kFLTFirebaseCrashlyticsChannelName = @"plugins.flutter.io/firebase_crashlytics"; + +// Argument Keys +NSString *const kCrashlyticsArgumentException = @"exception"; +NSString *const kCrashlyticsArgumentInformation = @"information"; +NSString *const kCrashlyticsArgumentStackTraceElements = @"stackTraceElements"; +NSString *const kCrashlyticsArgumentContext = @"context"; +NSString *const kCrashlyticsArgumentIdentifier = @"identifier"; +NSString *const kCrashlyticsArgumentKey = @"key"; +NSString *const kCrashlyticsArgumentValue = @"value"; + +NSString *const kCrashlyticsArgumentFile = @"file"; +NSString *const kCrashlyticsArgumentLine = @"line"; +NSString *const kCrashlyticsArgumentMethod = @"method"; + +NSString *const kCrashlyticsArgumentEnabled = @"enabled"; +NSString *const kCrashlyticsArgumentUnsentReports = @"unsentReports"; +NSString *const kCrashlyticsArgumentDidCrashOnPreviousExecution = @"didCrashOnPreviousExecution"; + +@interface FLTFirebaseCrashlyticsPlugin () +@end + +@implementation FLTFirebaseCrashlyticsPlugin + +#pragma mark - FlutterPlugin + +// Returns a singleton instance of the Firebase Crashlytics plugin. ++ (instancetype)sharedInstance { + static dispatch_once_t onceToken; + static FLTFirebaseCrashlyticsPlugin *instance; + + dispatch_once(&onceToken, ^{ + instance = [[FLTFirebaseCrashlyticsPlugin alloc] init]; + // Register with the Flutter Firebase plugin registry. + [[FLTFirebasePluginRegistry sharedInstance] registerFirebasePlugin:instance]; + }); + + return instance; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:kFLTFirebaseCrashlyticsChannelName + binaryMessenger:[registrar messenger]]; + FLTFirebaseCrashlyticsPlugin *instance = [FLTFirebaseCrashlyticsPlugin sharedInstance]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutterResult { + FLTFirebaseMethodCallErrorBlock errorBlock = + ^(NSString *_Nullable code, NSString *_Nullable message, NSDictionary *_Nullable details, + NSError *_Nullable error) { + // `result.error` is not called in this plugin so this block does nothing. + flutterResult(nil); + }; + + FLTFirebaseMethodCallResult *methodCallResult = + [FLTFirebaseMethodCallResult createWithSuccess:flutterResult andErrorBlock:errorBlock]; + + if ([@"Crashlytics#recordError" isEqualToString:call.method]) { + [self recordError:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#setUserIdentifier" isEqualToString:call.method]) { + [self setUserIdentifier:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#setCustomKey" isEqualToString:call.method]) { + [self setCustomKey:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#log" isEqualToString:call.method]) { + [self log:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#crash" isEqualToString:call.method]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" + @throw + [NSException exceptionWithName:@"FirebaseCrashlyticsTestCrash" + reason:@"This is a test crash caused by calling .crash() in Dart." + userInfo:nil]; +#pragma clang diagnostic pop + } else if ([@"Crashlytics#setCrashlyticsCollectionEnabled" isEqualToString:call.method]) { + [self setCrashlyticsCollectionEnabled:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#checkForUnsentReports" isEqualToString:call.method]) { + [self checkForUnsentReportsWithMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#sendUnsentReports" isEqualToString:call.method]) { + [self sendUnsentReportsWithMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#deleteUnsentReports" isEqualToString:call.method]) { + [self deleteUnsentReportsWithMethodCallResult:methodCallResult]; + } else if ([@"Crashlytics#didCrashDuringPreviousExecution" isEqualToString:call.method]) { + [self didCrashOnPreviousExecutionWithMethodCallResult:methodCallResult]; + } else { + methodCallResult.success(FlutterMethodNotImplemented); + } +} + +#pragma mark - Firebase Crashlytics API + +- (void)recordError:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + NSString *context = arguments[kCrashlyticsArgumentContext]; + NSString *information = arguments[kCrashlyticsArgumentInformation]; + NSString *dartExceptionMessage = arguments[kCrashlyticsArgumentException]; + NSArray *errorElements = arguments[kCrashlyticsArgumentStackTraceElements]; + + // Log additional information so it's captured on the Firebase Crashlytics dashboard. + if ([information length] != 0) { + [[FIRCrashlytics crashlytics] logWithFormat:@"%@", information]; + } + + // Report crash. + NSMutableArray *frames = [NSMutableArray array]; + for (NSDictionary *errorElement in errorElements) { + [frames addObject:[self generateFrame:errorElement]]; + } + + NSString *reason; + if (![context isEqual:[NSNull null]]) { + reason = [NSString stringWithFormat:@"%@. Error thrown %@.", dartExceptionMessage, context]; + // Log additional custom value to match Android. + [[FIRCrashlytics crashlytics] setCustomValue:[NSString stringWithFormat:@"thrown %@", context] + forKey:@"flutter_error_reason"]; + } else { + reason = dartExceptionMessage; + } + + // Log additional custom value to match Android. + [[FIRCrashlytics crashlytics] setCustomValue:dartExceptionMessage + forKey:@"flutter_error_exception"]; + + FIRExceptionModel *exception = [FIRExceptionModel exceptionModelWithName:@"FlutterError" + reason:reason]; + + exception.stackTrace = frames; + [[FIRCrashlytics crashlytics] recordExceptionModel:exception]; + result.success(nil); +} + +- (void)setUserIdentifier:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + [[FIRCrashlytics crashlytics] setUserID:arguments[kCrashlyticsArgumentIdentifier]]; + result.success(nil); +} + +- (void)setCustomKey:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + NSString *key = arguments[kCrashlyticsArgumentKey]; + NSString *value = arguments[kCrashlyticsArgumentValue]; + [[FIRCrashlytics crashlytics] setCustomValue:value forKey:key]; + result.success(nil); +} + +- (void)log:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + NSString *msg = arguments[@"message"]; + [[FIRCrashlytics crashlytics] logWithFormat:@"%@", msg]; + result.success(nil); +} + +- (void)setCrashlyticsCollectionEnabled:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + BOOL enabled = [arguments[kCrashlyticsArgumentEnabled] boolValue]; + [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:enabled]; + result.success(@{ + @"isCrashlyticsCollectionEnabled" : + @([FIRCrashlytics crashlytics].isCrashlyticsCollectionEnabled) + }); +} + +- (void)checkForUnsentReportsWithMethodCallResult:(FLTFirebaseMethodCallResult *)result { + [[FIRCrashlytics crashlytics] checkForUnsentReportsWithCompletion:^(BOOL unsentReports) { + result.success(@{kCrashlyticsArgumentUnsentReports : @(unsentReports)}); + }]; +} + +- (void)sendUnsentReportsWithMethodCallResult:(FLTFirebaseMethodCallResult *)result { + [[FIRCrashlytics crashlytics] sendUnsentReports]; + result.success(nil); +} + +- (void)deleteUnsentReportsWithMethodCallResult:(FLTFirebaseMethodCallResult *)result { + [[FIRCrashlytics crashlytics] deleteUnsentReports]; + result.success(nil); +} + +- (void)didCrashOnPreviousExecutionWithMethodCallResult:(FLTFirebaseMethodCallResult *)result { + BOOL didCrash = [[FIRCrashlytics crashlytics] didCrashDuringPreviousExecution]; + result.success(@{kCrashlyticsArgumentDidCrashOnPreviousExecution : @(didCrash)}); +} + +#pragma mark - Utilities + +- (FIRStackFrame *)generateFrame:(NSDictionary *)errorElement { + FIRStackFrame *frame = [FIRStackFrame + stackFrameWithSymbol:[errorElement valueForKey:kCrashlyticsArgumentMethod] + file:[errorElement valueForKey:kCrashlyticsArgumentFile] + line:[[errorElement valueForKey:kCrashlyticsArgumentLine] intValue]]; + return frame; +} + +#pragma mark - FLTFirebasePlugin + +- (void)didReinitializeFirebaseCore:(void (^)(void))completion { + // Not required for this plugin, nothing to cleanup between reloads. + completion(); +} + +- (NSDictionary *_Nonnull)pluginConstantsForFIRApp:(FIRApp *)firebase_app { + return @{ + @"isCrashlyticsCollectionEnabled" : + @([FIRCrashlytics crashlytics].isCrashlyticsCollectionEnabled) + }; +} + +- (NSString *_Nonnull)firebaseLibraryName { + return LIBRARY_NAME; +} + +- (NSString *_Nonnull)firebaseLibraryVersion { + return LIBRARY_VERSION; +} + +- (NSString *_Nonnull)flutterChannelName { + return kFLTFirebaseCrashlyticsChannelName; +} + +@end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec new file mode 100644 index 000000000000..fbd8e10de6b4 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec @@ -0,0 +1,43 @@ +require 'yaml' + +pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) +library_version = pubspec['version'].gsub('+', '-') + +firebase_sdk_version = '6.26.0' +if defined?($FirebaseSDKVersion) + Pod::UI.puts "#{pubspec['name']}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'" + firebase_sdk_version = $FirebaseSDKVersion +else + firebase_core_script = File.join(File.expand_path('..', File.expand_path('..', File.dirname(__FILE__))), 'firebase_core/ios/firebase_sdk_version.rb') + if File.exist?(firebase_core_script) + require firebase_core_script + firebase_sdk_version = firebase_sdk_version! + Pod::UI.puts "#{pubspec['name']}: Using Firebase SDK version '#{firebase_sdk_version}' defined in 'firebase_core'" + end +end + +Pod::Spec.new do |s| + s.name = pubspec['name'] + s.version = library_version + s.summary = pubspec['description'] + s.description = pubspec['description'] + s.homepage = pubspec['homepage'] + s.license = { :file => '../LICENSE' } + s.authors = 'The Chromium Authors' + s.source = { :path => '.' } + + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + + s.ios.deployment_target = '8.0' + + s.dependency 'firebase_core' + s.dependency 'Firebase/CoreOnly', "~> #{firebase_sdk_version}" + s.dependency 'Firebase/Crashlytics', "~> #{firebase_sdk_version}" + + s.static_framework = true + s.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{library_version}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-cls\\\"", + 'DEFINES_MODULE' => 'YES' + } +end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/lib/firebase_crashlytics.dart b/packages/firebase_crashlytics/firebase_crashlytics/lib/firebase_crashlytics.dart new file mode 100644 index 000000000000..0c99753125de --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/lib/firebase_crashlytics.dart @@ -0,0 +1,18 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library firebase_crashlytics; + +import 'package:flutter/foundation.dart' + show kDebugMode, FlutterErrorDetails, FlutterError, DiagnosticsNode; +import 'package:stack_trace/stack_trace.dart'; + +import 'package:firebase_crashlytics_platform_interface/firebase_crashlytics_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' + show FirebasePluginPlatform; + +import 'src/utils.dart'; + +part 'src/firebase_crashlytics.dart'; diff --git a/packages/firebase_crashlytics/firebase_crashlytics/lib/src/firebase_crashlytics.dart b/packages/firebase_crashlytics/firebase_crashlytics/lib/src/firebase_crashlytics.dart new file mode 100644 index 000000000000..15922e861471 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/lib/src/firebase_crashlytics.dart @@ -0,0 +1,239 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +part of firebase_crashlytics; + +/// The entry point for accessing a [FirebaseCrashlytics]. +/// +/// You can get an instance by calling [FirebaseCrashlytics.instance]. +class FirebaseCrashlytics extends FirebasePluginPlatform { + /// Cached instance of [FirebaseCrashlytics]; + static FirebaseCrashlytics _instance; + + // Cached and lazily loaded instance of [FirebaseCrashlyticsPlatform] to avoid + // creating a [MethodChannelFirebaseCrashlytics] when not needed or creating an + // instance with the default app before a user specifies an app. + FirebaseCrashlyticsPlatform _delegatePackingProperty; + + FirebaseCrashlyticsPlatform get _delegate { + return _delegatePackingProperty ??= FirebaseCrashlyticsPlatform.instanceFor( + app: app, pluginConstants: pluginConstants); + } + + /// The [FirebaseApp] for this current [FirebaseCrashlytics] instance. + FirebaseApp app; + + FirebaseCrashlytics._({this.app}) + : super(app.name, 'plugins.flutter.io/firebase_crashlytics'); + + /// Returns an instance using the default [FirebaseApp]. + static FirebaseCrashlytics get instance { + return _instance ??= FirebaseCrashlytics._(app: Firebase.app()); + } + + /// Whether the current Crashlytics instance is collecting reports. If false, + /// then no crash reporting data is sent to Firebase. + /// + /// See [setCrashlyticsCollectionEnabled] for toggling collection status. + @Deprecated( + "enableInDevMode getter is deprecated, use 'isCrashlyticsCollectionEnabled' instead") + bool get enableInDevMode { + return _delegate.isCrashlyticsCollectionEnabled; + } + + /// Whether the current Crashlytics instance is collecting reports. If false, + /// then no crash reporting data is sent to Firebase. + @Deprecated( + "enableInDevMode setter is deprecated, use 'setCrashlyticsCollectionEnabled(bool)' instead") + set enableInDevMode(bool enabled) { + _delegate.setCrashlyticsCollectionEnabled(enabled).then((value) => null); + } + + /// Whether the current Crashlytics instance is collecting reports. If false, + /// then no crash reporting data is sent to Firebase. + /// + /// See [setCrashlyticsCollectionEnabled] for toggling collection status. + bool get isCrashlyticsCollectionEnabled { + return _delegate.isCrashlyticsCollectionEnabled; + } + + /// Checks a device for any fatal or non-fatal crash reports that haven't yet + /// been sent to Crashlytics. + /// + /// If automatic data collection is enabled, then reports are uploaded + /// automatically and this always returns false. If automatic data collection + /// is disabled, this method can be used to check whether the user opts-in to + /// send crash reports from their device. + Future checkForUnsentReports() { + return _delegate.checkForUnsentReports(); + } + + /// Causes the app to crash (natively). + /// + /// This should only be used for testing purposes in cases where you wish to + /// simulate a native crash to view the results on the Firebase Console. + /// + /// Note: crash reports will not include a stack trace. + void crash() { + return _delegate.crash(); + } + + /// If automatic data collection is disabled, this method queues up all the + /// reports on a device for deletion. Otherwise, this method is a no-op. + Future deleteUnsentReports() { + return _delegate.deleteUnsentReports(); + } + + /// Checks whether the app crashed on its previous run. + Future didCrashOnPreviousExecution() { + return _delegate.didCrashOnPreviousExecution(); + } + + /// Submits a Crashlytics report of a caught error. + Future recordError(dynamic exception, StackTrace stack, + {dynamic context, + Iterable information, + bool printDetails}) async { + // If [null] is provided, use the debug flag instead. + printDetails ??= kDebugMode; + + final String _information = (information == null || information.isEmpty) + ? '' + : (StringBuffer()..writeAll(information, '\n')).toString(); + + if (printDetails) { + print('----------------FIREBASE CRASHLYTICS----------------'); + + // If available, give context to the exception. + if (context != null) { + print('The following exception was thrown $context:'); + } + + // Need to print the exception to explain why the exception was thrown. + print(exception); + + // Print information provided by the Flutter framework about the exception. + if (_information.isNotEmpty) print('\n$_information'); + + // Not using Trace.format here to stick to the default stack trace format + // that Flutter developers are used to seeing. + if (stack != null) print('\n$stack'); + print('----------------------------------------------------'); + } + + // 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. + stack ??= StackTrace.current ?? StackTrace.fromString(''); + + // Report error. + final List stackTraceLines = + Trace.format(stack).trimRight().split('\n'); + final List> stackTraceElements = + getStackTraceElements(stackTraceLines); + + return _delegate.recordError( + exception: exception.toString(), + context: context?.toString(), + information: _information, + stackTraceElements: stackTraceElements, + ); + } + + /// Submits a Crashlytics report of a non-fatal error caught by the Flutter framework. + Future recordFlutterError(FlutterErrorDetails flutterErrorDetails) { + assert(flutterErrorDetails != null); + FlutterError.dumpErrorToConsole(flutterErrorDetails, forceReport: true); + return recordError( + flutterErrorDetails.exceptionAsString(), flutterErrorDetails.stack, + context: flutterErrorDetails.context, + printDetails: false, + information: flutterErrorDetails.informationCollector == null + ? null + : flutterErrorDetails.informationCollector()); + } + + /// Logs a message that's included in the next fatal or non-fatal report. + /// + /// Logs are visible in the session view on the Firebase Crashlytics console. + /// + /// Newline characters are stripped and extremely long messages are truncated. + /// The maximum log size is 64k. If exceeded, the log rolls such that messages + /// are removed, starting from the oldest. + Future log(String message) async { + assert(message != null); + return _delegate.log(message); + } + + /// If automatic data collection is disabled, this method queues up all the + /// reports on a device to send to Crashlytics. Otherwise, this method is a no-op. + Future sendUnsentReports() { + return _delegate.sendUnsentReports(); + } + + /// Enables/disables automatic data collection by Crashlytics. + /// + /// If this is set, it overrides the data collection settings provided by the + /// Android Manifest, iOS Plist settings, as well as any Firebase-wide automatic + /// data collection settings. + /// + /// If automatic data collection is disabled for Crashlytics, crash reports are + /// stored on the device. To check for reports, use the [checkForUnsentReports] + /// method. Use [sendUnsentReports] to upload existing reports even when automatic + /// data collection is disabled. Use [deleteUnsentReports] to delete any reports + /// stored on the device without sending them to Crashlytics. + Future setCrashlyticsCollectionEnabled(bool enabled) { + assert(enabled != null); + return _delegate.setCrashlyticsCollectionEnabled(enabled); + } + + /// Records a user ID (identifier) that's associated with subsequent fatal and + /// non-fatal reports. + /// + /// The user ID is visible in the session view on the Firebase Crashlytics console. + /// Identifiers longer than 1024 characters will be truncated. + /// + /// Ensure you have collected permission to store any personal identifiable information + /// from the user if required. + Future setUserIdentifier(String identifier) { + assert(identifier != null); + return _delegate.setUserIdentifier(identifier); + } + + /// Sets a custom key and value that are associated with subsequent fatal and + /// non-fatal reports. + /// + /// Multiple calls to this method with the same key update the value for that key. + /// The value of any key at the time of a fatal or non-fatal event is associated + /// with that event. Keys and associated values are visible in the session view + /// on the Firebase Crashlytics console. + /// + /// Accepts a maximum of 64 key/value pairs. New keys beyond that limit are + /// ignored. Keys or values that exceed 1024 characters are truncated. + /// + /// The value can only be a type [int], [num], [String] or [bool]. + Future setCustomKey(String key, dynamic value) async { + assert(key != null); + assert(value != null); + assert(value is int || value is num || value is String || value is bool); + return _delegate.setCustomKey(key, value.toString()); + } +} + +/// Extends the [FirebaseCrashlytics] class to allow for deprecated usage of +/// using [Crashlytics] directly. +@Deprecated( + "Class Crashlytics is deprecated. Use 'FirebaseCrashlytics' instead.") +class Crashlytics extends FirebaseCrashlytics { + // ignore: public_member_api_docs + @Deprecated( + "Constructing Crashlytics is deprecated, use 'FirebaseCrashlytics.instance' instead") + factory Crashlytics() { + return FirebaseCrashlytics.instance; + } + + @Deprecated( + "Accessing Crashlytics.instance is deprecated, use 'FirebaseCrashlytics.instance' instead") + // ignore: public_member_api_docs + static FirebaseCrashlytics get instance => FirebaseCrashlytics.instance; +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/lib/src/utils.dart b/packages/firebase_crashlytics/firebase_crashlytics/lib/src/utils.dart new file mode 100644 index 000000000000..1070d7194620 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/lib/src/utils.dart @@ -0,0 +1,41 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Returns a [List] containing detailed output of each line in a stack trace. +List> getStackTraceElements(List lines) { + final List> elements = >[]; + + for (String line in lines) { + final List lineParts = line.split(RegExp('\\s+')); + + try { + final String fileName = lineParts.first; + + // Sometimes the trace looks like [,] and doesn't contain a line field + final String lineNumber = + lineParts.length > 2 ? lineParts[1].split(":").first : "0"; + + final Map element = { + 'file': fileName, + 'line': lineNumber, + }; + + final List methodField = lineParts.last.split("."); + + final String methodName = methodField.last.trim(); + element['method'] = methodName; + + if (methodField.length > 1) { + final String className = methodField.first.trim(); + element['class'] = className; + } + + elements.add(element); + } catch (e) { + print(e.toString()); + } + } + + return elements; +} diff --git a/packages/firebase_crashlytics/macos/Assets/.gitkeep b/packages/firebase_crashlytics/firebase_crashlytics/macos/Assets/.gitkeep similarity index 100% rename from packages/firebase_crashlytics/macos/Assets/.gitkeep rename to packages/firebase_crashlytics/firebase_crashlytics/macos/Assets/.gitkeep diff --git a/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.h b/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.h new file mode 120000 index 000000000000..3e6717fc31b9 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.h @@ -0,0 +1 @@ +./../../ios/Classes/FLTFirebaseCrashlyticsPlugin.h \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.m new file mode 120000 index 000000000000..300d0a077f1e --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/macos/Classes/FLTFirebaseCrashlyticsPlugin.m @@ -0,0 +1 @@ +./../../ios/Classes/FLTFirebaseCrashlyticsPlugin.m \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/macos/firebase_crashlytics.podspec b/packages/firebase_crashlytics/firebase_crashlytics/macos/firebase_crashlytics.podspec new file mode 100644 index 000000000000..8d9232aa5fa2 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/macos/firebase_crashlytics.podspec @@ -0,0 +1,47 @@ +require 'yaml' + +pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) +library_version = pubspec['version'].gsub('+', '-') + +firebase_sdk_version = '6.26.0' +if defined?($FirebaseSDKVersion) + Pod::UI.puts "#{pubspec['name']}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'" + firebase_sdk_version = $FirebaseSDKVersion +else + firebase_core_script = File.join(File.expand_path('..', File.expand_path('..', File.dirname(__FILE__))), 'firebase_core/ios/firebase_sdk_version.rb') + if File.exist?(firebase_core_script) + require firebase_core_script + firebase_sdk_version = firebase_sdk_version! + Pod::UI.puts "#{pubspec['name']}: Using Firebase SDK version '#{firebase_sdk_version}' defined in 'firebase_core'" + end +end + +Pod::Spec.new do |s| + s.name = pubspec['name'] + s.version = library_version + s.summary = pubspec['description'] + s.description = pubspec['description'] + s.homepage = pubspec['homepage'] + s.license = { :file => '../LICENSE' } + s.authors = 'The Chromium Authors' + s.source = { :path => '.' } + + s.source_files = 'Classes/**/*.{h,m}' + s.public_header_files = 'Classes/**/*.h' + + s.platform = :osx, '10.11' + + # Flutter dependencies + s.dependency 'FlutterMacOS' + + # Firebase dependencies + s.dependency 'firebase_core' + s.dependency 'Firebase/CoreOnly', "~> #{firebase_sdk_version}" + s.dependency 'Firebase/Crashlytics', "~> #{firebase_sdk_version}" + + s.static_framework = true + s.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{library_version}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-cls\\\"", + 'DEFINES_MODULE' => 'YES' + } +end diff --git a/packages/firebase_crashlytics/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml similarity index 50% rename from packages/firebase_crashlytics/pubspec.yaml rename to packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml index 4493ffc7314a..23b3008cb199 100644 --- a/packages/firebase_crashlytics/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml @@ -2,34 +2,35 @@ 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-dev.5 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" +flutter: + plugin: + platforms: + android: + package: io.flutter.plugins.firebase.crashlytics + pluginClass: FlutterFirebaseCrashlyticsPlugin + ios: + pluginClass: FLTFirebaseCrashlyticsPlugin + macos: + pluginClass: FLTFirebaseCrashlyticsPlugin dependencies: flutter: sdk: flutter - stack_trace: ^1.9.3 + stack_trace: ^1.9.5 + firebase_core: ^0.5.0 + firebase_core_platform_interface: ^2.0.0 + firebase_crashlytics_platform_interface: ^1.0.0-dev.2 dev_dependencies: pedantic: ^1.8.0 flutter_test: sdk: flutter test: ^1.5.1 - flutter_driver: - sdk: flutter - e2e: ^0.6.1 -flutter: - plugin: - platforms: - android: - package: io.flutter.plugins.firebase.crashlytics.firebasecrashlytics - pluginClass: FirebaseCrashlyticsPlugin - ios: - pluginClass: FirebaseCrashlyticsPlugin - macos: - pluginClass: FirebaseCrashlyticsPlugin +environment: + sdk: ">=2.8.0 <3.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" + diff --git a/packages/firebase_crashlytics/firebase_crashlytics/test/firebase_crashlytics_test.dart b/packages/firebase_crashlytics/firebase_crashlytics/test/firebase_crashlytics_test.dart new file mode 100644 index 000000000000..e2366ef54e3b --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/test/firebase_crashlytics_test.dart @@ -0,0 +1,269 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:firebase_crashlytics/src/utils.dart'; +import 'package:stack_trace/stack_trace.dart'; +import './mock.dart'; + +void main() { + setupFirebaseCrashlyticsMocks(); + + FirebaseCrashlytics crashlytics; + + group('$FirebaseCrashlytics', () { + setUpAll(() async { + await Firebase.initializeApp(); + crashlytics = FirebaseCrashlytics.instance; + }); + + setUp(() async { + methodCallLog.clear(); + }); + + tearDown(() { + methodCallLog.clear(); + }); + + test('checkForUnsentReports', () async { + await crashlytics.checkForUnsentReports(); + + expect(methodCallLog, [ + isMethodCall('Crashlytics#checkForUnsentReports', arguments: null) + ]); + }); + + test('crash', () async { + await crashlytics.crash(); + + expect(methodCallLog, + [isMethodCall('Crashlytics#crash', arguments: null)]); + }); + + test('deleteUnsentReports', () async { + await crashlytics.deleteUnsentReports(); + + expect(methodCallLog, [ + isMethodCall('Crashlytics#deleteUnsentReports', arguments: null) + ]); + }); + + test('didCrashOnPreviousExecution', () async { + await crashlytics.didCrashOnPreviousExecution(); + + expect(methodCallLog, [ + isMethodCall('Crashlytics#didCrashOnPreviousExecution', arguments: null) + ]); + }); + + group('recordError', () { + test('with stack', () async { + final stack = StackTrace.current; + final exception = 'foo exception'; + final exceptionContext = 'bar context'; + + await crashlytics.recordError(exception, stack, + context: exceptionContext, printDetails: false); + expect(methodCallLog, [ + isMethodCall('Crashlytics#recordError', arguments: { + 'exception': exception, + 'context': exceptionContext, + 'information': '', + 'stackTraceElements': getStackTraceElements( + Trace.format(stack).trimRight().split('\n')) + }) + ]); + // Confirm that the stack trace contains current stack. + expect( + methodCallLog[0].arguments['stackTraceElements'], + contains( + containsPair('file', contains('firebase_crashlytics_test.dart'))), + ); + }); + + test('without stack', () async { + final exception = 'foo exception'; + final exceptionContext = 'bar context'; + + await crashlytics.recordError(exception, null, + context: exceptionContext); + expect(methodCallLog[0].method, 'Crashlytics#recordError'); + expect(methodCallLog[0].arguments['exception'], exception); + expect(methodCallLog[0].arguments['context'], exceptionContext); + + // Confirm that the stack trace contains current stack. + expect( + methodCallLog[0].arguments['stackTraceElements'], + contains( + containsPair('file', contains('firebase_crashlytics_test.dart'))), + ); + }); + }); + + test('recordFlutterError', () async { + final exception = 'foo exception'; + final exceptionContext = 'bar context'; + final exceptionLibrary = 'baz library'; + final exceptionFirstMessage = 'first message'; + final exceptionSecondMessage = 'second message'; + final stack = StackTrace.current; + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stack, + library: exceptionLibrary, + informationCollector: () => [ + DiagnosticsNode.message(exceptionFirstMessage), + DiagnosticsNode.message(exceptionSecondMessage), + ], + context: ErrorDescription(exceptionContext), + ); + await crashlytics.recordFlutterError(details); + expect(methodCallLog, [ + isMethodCall('Crashlytics#recordError', arguments: { + 'exception': exception, + 'context': exceptionContext, + 'information': '$exceptionFirstMessage\n$exceptionSecondMessage', + 'stackTraceElements': + getStackTraceElements(Trace.format(stack).trimRight().split('\n')) + }) + ]); + }); + + group('log', () { + test('should throw if msg is null', () async { + expect(() => crashlytics.log(null), throwsAssertionError); + }); + + test('should call delegate method', () async { + final msg = 'foo'; + await crashlytics.log(msg); + expect(methodCallLog, [ + isMethodCall('Crashlytics#log', arguments: { + 'message': msg, + }) + ]); + }); + }); + + group('sendUnsentReports', () { + test('should call delegate method', () async { + await crashlytics.sendUnsentReports(); + expect(methodCallLog, [ + isMethodCall('Crashlytics#sendUnsentReports', arguments: null) + ]); + }); + }); + + group('setCrashlyticsCollectionEnabled', () { + test('should throw if null', () async { + expect(() => crashlytics.setCrashlyticsCollectionEnabled(null), + throwsAssertionError); + }); + + test('should call delegate method', () async { + await crashlytics.setCrashlyticsCollectionEnabled(false); + expect(crashlytics.isCrashlyticsCollectionEnabled, isFalse); + await crashlytics.setCrashlyticsCollectionEnabled(true); + expect(crashlytics.isCrashlyticsCollectionEnabled, isTrue); + expect(methodCallLog, [ + isMethodCall('Crashlytics#setCrashlyticsCollectionEnabled', + arguments: { + 'enabled': false, + }), + isMethodCall('Crashlytics#setCrashlyticsCollectionEnabled', + arguments: { + 'enabled': true, + }) + ]); + }); + }); + + group('setUserIdentifier', () { + test('should throw if null', () async { + expect(() => crashlytics.setUserIdentifier(null), throwsAssertionError); + }); + + test('should call delegate method', () async { + final id = 'foo'; + await crashlytics.setUserIdentifier(id); + expect(methodCallLog, [ + isMethodCall('Crashlytics#setUserIdentifier', arguments: { + 'identifier': id, + }) + ]); + }); + }); + + group('setCustomKey', () { + test('should throw if null', () async { + expect( + () => crashlytics.setCustomKey(null, null), throwsAssertionError); + expect( + () => crashlytics.setCustomKey('foo', null), throwsAssertionError); + expect(() => crashlytics.setCustomKey('foo', []), throwsAssertionError); + expect(() => crashlytics.setCustomKey('foo', {}), throwsAssertionError); + }); + + test('should call delegate method', () async { + final key = 'foo'; + final value = 'bar'; + await crashlytics.setCustomKey(key, value); + expect(methodCallLog, [ + isMethodCall('Crashlytics#setCustomKey', arguments: { + 'key': key, + 'value': value, + }) + ]); + }); + }); + + group('getStackTraceElements', () { + test('with character index', () async { + final List lines = [ + 'package:flutter/src/widgets/framework.dart 3825:27 StatefulElement.build' + ]; + final List> elements = getStackTraceElements(lines); + expect(elements.length, 1); + expect(elements.first, { + 'class': 'StatefulElement', + 'method': 'build', + 'file': 'package:flutter/src/widgets/framework.dart', + 'line': '3825', + }); + }); + + test('without character index', () async { + final List lines = [ + 'package:flutter/src/widgets/framework.dart 3825 StatefulElement.build' + ]; + final List> elements = getStackTraceElements(lines); + expect(elements.length, 1); + expect(elements.first, { + 'class': 'StatefulElement', + 'method': 'build', + 'file': 'package:flutter/src/widgets/framework.dart', + 'line': '3825', + }); + }); + + test('without class', () async { + final List lines = [ + 'package:firebase_crashlytics/test/main.dart 12 main' + ]; + final List> elements = getStackTraceElements(lines); + expect(elements.length, 1); + expect(elements.first, { + 'method': 'main', + 'file': 'package:firebase_crashlytics/test/main.dart', + 'line': '12', + }); + }); + }); + }); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/test/mock.dart b/packages/firebase_crashlytics/firebase_crashlytics/test/mock.dart new file mode 100644 index 000000000000..883553e279e2 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/test/mock.dart @@ -0,0 +1,78 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_crashlytics_platform_interface/firebase_crashlytics_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +typedef Callback(MethodCall call); + +final List methodCallLog = []; + +setupFirebaseCrashlyticsMocks([Callback customHandlers]) { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': { + 'plugins.flutter.io/firebase_crashlytics': { + 'isCrashlyticsCollectionEnabled': true + } + }, + } + ]; + } + + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': { + 'plugins.flutter.io/firebase_crashlytics': { + 'isCrashlyticsCollectionEnabled': true + } + }, + }; + } + + if (customHandlers != null) { + customHandlers(call); + } + + return null; + }); + + MethodChannelFirebaseCrashlytics.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + methodCallLog.add(methodCall); + switch (methodCall.method) { + case 'Crashlytics#checkForUnsentReports': + return { + 'unsentReports': true, + }; + case 'Crashlytics#setCrashlyticsCollectionEnabled': + return { + 'isCrashlyticsCollectionEnabled': methodCall.arguments['enabled'] + }; + case 'Crashlytics#didCrashOnPreviousExecution': + return { + 'didCrashOnPreviousExecution': true, + }; + case 'Crashlytics#recordError': + return null; + default: + return false; + } + }); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/.gitignore b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/.gitignore new file mode 100644 index 000000000000..19237fbdf296 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/.gitignore @@ -0,0 +1,77 @@ +AppCodeIntelliJ IDEAPhpStormWebStorm +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ +.metadata + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..2d3d9b527a9d --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/CHANGELOG.md @@ -0,0 +1,7 @@ +## 1.0.0-dev.2 + +* Fixed various code documentation typos. + +## 1.0.0-dev.1 + +* Initial release of the `firebase_crashlytics_platform_interface` package. diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/LICENSE b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/LICENSE new file mode 100644 index 000000000000..9d40552c335e --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/LICENSE @@ -0,0 +1,26 @@ +Copyright 2020, the Chromium project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/README.md b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/README.md new file mode 100644 index 000000000000..f7201070716c --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/README.md @@ -0,0 +1,26 @@ +# firebase_crashlytics_platform_interface + +A common platform interface for the [`firebase_crashlytics`][1] plugin. + +This interface allows platform-specific implementations of the `firebase_crashlytics` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +## Usage + +To implement a new platform-specific implementation of `firebase_crashlytics`, extend +[`FirebaseCrashlyticsPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`FirebaseCrashlyticsPlatform` by calling +`FirebaseCrashlyticsPlatform.instance = MyCrashlytics()`. + +## Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../firebase_crashlytics +[2]: lib/firebase_crashlytics_platform_interface.dart diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/firebase_crashlytics_platform_interface.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/firebase_crashlytics_platform_interface.dart new file mode 100644 index 000000000000..72bccb45458f --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/firebase_crashlytics_platform_interface.dart @@ -0,0 +1,8 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library firebase_crashlytics_platform_interface; + +export 'src/platform_interface/platform_interface_crashlytics.dart'; +export 'src/method_channel/method_channel_crashlytics.dart'; diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/method_channel_crashlytics.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/method_channel_crashlytics.dart new file mode 100644 index 000000000000..857681fdfe5e --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/method_channel_crashlytics.dart @@ -0,0 +1,128 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +import './utils/exception.dart'; +import '../platform_interface/platform_interface_crashlytics.dart'; + +/// The entry point for accessing a method channel based Crashlytics instance. +/// +/// You can get an instance by calling [MethodChannelFirebaseCrashlytics.instance]. +class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { + /// Create an instance of [MethodChannelFirebaseCrashlytics]. + MethodChannelFirebaseCrashlytics({FirebaseApp app}) : super(appInstance: app); + + /// The [MethodChannel] used to communicate with the native plugin + static MethodChannel channel = MethodChannel( + 'plugins.flutter.io/firebase_crashlytics', + ); + + bool _isCrashlyticsCollectionEnabled; + + @override + bool get isCrashlyticsCollectionEnabled { + return _isCrashlyticsCollectionEnabled; + } + + @override + MethodChannelFirebaseCrashlytics setInitialValues({ + bool isCrashlyticsCollectionEnabled, + }) { + this._isCrashlyticsCollectionEnabled = isCrashlyticsCollectionEnabled; + return this; + } + + @override + Future checkForUnsentReports() async { + Map data = await channel + .invokeMapMethod('Crashlytics#checkForUnsentReports') + .catchError(catchPlatformException); + + return data['unsentReports']; + } + + @override + Future crash() { + return channel + .invokeMethod('Crashlytics#crash') + .catchError(catchPlatformException); + } + + @override + Future deleteUnsentReports() { + return channel + .invokeMethod('Crashlytics#deleteUnsentReports') + .catchError(catchPlatformException); + } + + @override + Future didCrashOnPreviousExecution() async { + Map data = await channel + .invokeMapMethod( + 'Crashlytics#didCrashOnPreviousExecution') + .catchError(catchPlatformException); + + return data['didCrashOnPreviousExecution']; + } + + @override + Future recordError({ + String exception, + String context, + String information, + List> stackTraceElements, + }) { + return channel + .invokeMethod('Crashlytics#recordError', { + 'exception': exception, + 'context': context, + 'information': information, + 'stackTraceElements': stackTraceElements ?? [], + }).catchError(catchPlatformException); + } + + @override + Future log(String message) { + return channel.invokeMethod('Crashlytics#log', { + 'message': message, + }).catchError(catchPlatformException); + } + + @override + Future sendUnsentReports() { + return channel + .invokeMethod('Crashlytics#sendUnsentReports') + .catchError(catchPlatformException); + } + + @override + Future setCrashlyticsCollectionEnabled(bool enabled) async { + Map data = await channel.invokeMapMethod( + 'Crashlytics#setCrashlyticsCollectionEnabled', { + 'enabled': enabled, + }).catchError(catchPlatformException); + _isCrashlyticsCollectionEnabled = data['isCrashlyticsCollectionEnabled']; + } + + @override + Future setUserIdentifier(String identifier) { + return channel + .invokeMethod('Crashlytics#setUserIdentifier', { + 'identifier': identifier, + }).catchError(catchPlatformException); + } + + @override + Future setCustomKey(String key, String value) { + return channel + .invokeMethod('Crashlytics#setCustomKey', { + 'key': key, + 'value': value, + }).catchError(catchPlatformException); + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/utils/exception.dart new file mode 100644 index 000000000000..e7a86fef2255 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/method_channel/utils/exception.dart @@ -0,0 +1,41 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +/// Catches a [PlatformException] and converts it into a [FirebaseException] if +/// it was intentionally caught on the native platform. +FutureOr> catchPlatformException(Object exception) async { + if (exception is! Exception || exception is! PlatformException) { + throw exception; + } + + throw platformExceptionToFirebaseException(exception as PlatformException); +} + +/// Converts a [PlatformException] into a [FirebaseException]. +/// +/// A [PlatformException] can only be converted to a [FirebaseException] if the +/// `details` of the exception exist. Firebase returns specific codes and messages +/// which can be converted into user friendly exceptions. +FirebaseException platformExceptionToFirebaseException( + PlatformException platformException) { + Map details = platformException.details != null + ? Map.from(platformException.details) + : null; + + String code = 'unknown'; + String message = platformException.message; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + return FirebaseException( + plugin: 'firebase_crashlytics', code: code, message: message); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/platform_interface/platform_interface_crashlytics.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/platform_interface/platform_interface_crashlytics.dart new file mode 100644 index 000000000000..e8c6b830ab58 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/platform_interface/platform_interface_crashlytics.dart @@ -0,0 +1,186 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../method_channel/method_channel_crashlytics.dart'; + +/// The Firebase Crashlytics platform interface. +/// +/// This class should be extended by any classes implementing the plugin on +/// other Flutter supported platforms. +abstract class FirebaseCrashlyticsPlatform extends PlatformInterface { + /// The [FirebaseApp] this instance was initialized with. + @protected + final FirebaseApp appInstance; + + /// Create an instance using [app] + FirebaseCrashlyticsPlatform({this.appInstance}) : super(token: _token); + + static final Object _token = Object(); + + /// Returns the [FirebaseApp] for the current instance. + FirebaseApp get app { + return appInstance ?? Firebase.app(); + } + + /// Create an instance using [app] using the existing implementation + factory FirebaseCrashlyticsPlatform.instanceFor( + {FirebaseApp app, Map pluginConstants}) { + // Only the default app is supported on Crashlytics. + assert(app.name == defaultFirebaseAppName); + assert(pluginConstants != null); + // Must have bool collection enabled constant. + assert(pluginConstants['isCrashlyticsCollectionEnabled'] != null); + return FirebaseCrashlyticsPlatform.instance.setInitialValues( + isCrashlyticsCollectionEnabled: + pluginConstants['isCrashlyticsCollectionEnabled'], + ); + } + + static FirebaseCrashlyticsPlatform _instance; + + /// The current default [FirebaseCrashlyticsPlatform] instance. + /// + /// It will always default to [MethodChannelFirebaseCrashlytics] + /// if no other implementation was provided. + static FirebaseCrashlyticsPlatform get instance { + return _instance ??= MethodChannelFirebaseCrashlytics(app: Firebase.app()); + } + + /// Sets the [FirebaseCrashlyticsPlatform.instance] + static set instance(FirebaseCrashlyticsPlatform instance) { + assert(instance != null); + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Whether the current Crashlytics instance is collecting reports. If false, + /// then no crash reporting data is sent to Firebase. + /// + /// See [setCrashlyticsCollectionEnabled] for toggling collection status. + bool get isCrashlyticsCollectionEnabled { + throw UnimplementedError( + "isCrashlyticsCollectionEnabled is not implemented"); + } + + /// Checks a device for any fatal or non-fatal crash reports that haven't yet + /// been sent to Crashlytics. + /// + /// If automatic data collection is enabled, then reports are uploaded + /// automatically and this always returns false. If automatic data collection + /// is disabled, this method can be used to check whether the user opts-in to + /// send crash reports from their device. + Future checkForUnsentReports() { + throw UnimplementedError('checkForUnsentReports() is not implemented'); + } + + /// Causes the app to crash (natively). + /// + /// This should only be used for testing purposes in cases where you wish to + /// simulate a native crash to view the results on the Firebase Console. + /// + /// Note: crash reports will not include a stack trace. + void crash() { + throw UnimplementedError('crash() is not implemented'); + } + + /// If automatic data collection is disabled, this method queues up all the + /// reports on a device for deletion. Otherwise, this method is a no-op. + Future deleteUnsentReports() { + throw UnimplementedError('deleteUnsentReports() is not implemented'); + } + + /// Checks whether the app crashed on its previous run. + Future didCrashOnPreviousExecution() { + throw UnimplementedError( + 'didCrashOnPreviousExecution() is not implemented'); + } + + /// Submits a Crashlytics report of a caught error. + Future recordError({ + String exception, + String context, + String information, + List> stackTraceElements, + }) { + throw UnimplementedError('recordError() is not implemented'); + } + + /// Logs a message that's included in the next fatal or non-fatal report. + /// + /// Logs are visible in the session view on the Firebase Crashlytics console. + /// + /// Newline characters are stripped and extremely long messages are truncated. + /// The maximum log size is 64k. If exceeded, the log rolls such that messages + /// are removed, starting from the oldest. + Future log(String message) { + throw UnimplementedError('log() is not implemented'); + } + + /// If automatic data collection is disabled, this method queues up all the + /// reports on a device to send to Crashlytics. Otherwise, this method is a no-op. + Future sendUnsentReports() { + throw UnimplementedError('sendUnsentReports() is not implemented'); + } + + /// Enables/disables automatic data collection by Crashlytics. + /// + /// If this is set, it overrides the data collection settings provided by the + /// Android Manifest, iOS Plist settings, as well as any Firebase-wide automatic + /// data collection settings. + /// + /// If automatic data collection is disabled for Crashlytics, crash reports are + /// stored on the device. To check for reports, use the [checkForUnsentReports] + /// method. Use [sendUnsentReports] to upload existing reports even when automatic + /// data collection is disabled. Use [deleteUnsentReports] to delete any reports + /// stored on the device without sending them to Crashlytics. + Future setCrashlyticsCollectionEnabled(bool enabled) { + throw UnimplementedError( + 'setCrashlyticsCollectionEnabled() is not implemented'); + } + + /// Records a user ID (identifier) that's associated with subsequent fatal and + /// non-fatal reports. + /// + /// The user ID is visible in the session view on the Firebase Crashlytics console. + /// Identifiers longer than 1024 characters will be truncated. + /// + /// Ensure you have collected permission to store any personal identifiable information + /// from the user if required. + Future setUserIdentifier(String identifier) { + throw UnimplementedError('setUserIdentifier() is not implemented'); + } + + /// Sets a custom key and value that are associated with subsequent fatal and + /// non-fatal reports. + /// + /// Multiple calls to this method with the same key update the value for that key. + /// The value of any key at the time of a fatal or non-fatal event is associated + /// with that event. Keys and associated values are visible in the session view + /// on the Firebase Crashlytics console. + /// + /// Accepts a maximum of 64 key/value pairs. New keys beyond that limit are + /// ignored. Keys or values that exceed 1024 characters are truncated. + Future setCustomKey(String key, String value) { + throw UnimplementedError('setCustomKey() is not implemented'); + } + + /// Sets any initial values on the instance. + /// + /// Platforms with Method Channels can provide constant values to be available + /// before the instance has initialized to prevent any unnecessary async + /// calls. + @protected + FirebaseCrashlyticsPlatform setInitialValues({ + bool isCrashlyticsCollectionEnabled, + }) { + throw UnimplementedError('setInitialValues() is not implemented'); + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..1c328a88f6bd --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml @@ -0,0 +1,23 @@ +name: firebase_crashlytics_platform_interface +description: A common platform interface for the firebase_crashlytics plugin. +version: 1.0.0-dev.2 +homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_crashlytics/firebase_crashlytics_platform_interface + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + collection: ^1.14.3 + firebase_core: ^0.5.0 + plugin_platform_interface: ^1.0.2 + +dev_dependencies: + firebase_core_platform_interface: ^2.0.0 + pedantic: ^1.8.0 + flutter_test: + sdk: flutter + mockito: ^4.1.1 + +environment: + sdk: ">=2.0.0 <3.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/method_channel_crashlytics_test.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/method_channel_crashlytics_test.dart new file mode 100644 index 000000000000..eb5f5d67e914 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/method_channel_crashlytics_test.dart @@ -0,0 +1,341 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics_platform_interface/firebase_crashlytics_platform_interface.dart'; +import 'package:firebase_crashlytics_platform_interface/src/method_channel/method_channel_crashlytics.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../mock.dart'; + +void main() { + setupFirebaseCrashlyticsMocks(); + + FirebaseCrashlyticsPlatform crashlytics; + final List logger = []; + + // mock props + bool mockPlatformExceptionThrown = false; + bool mockExceptionThrown = false; + + bool kUnsentReports = false; + + String kMockMessage = 'foo.bar.baz'; + String kMockUserIdentifier = 'user12345'; + + Map kMockError = { + 'exception': 'Test exception', + 'context': 'MethodChannelTest', + 'information': 'This is a test exception', + 'stackTraceElements': >[ + { + 'declaringClass': 'MethodChannelCrashlyticsTest', + 'methodName': 'recordError', + 'fileName': 'method_channel_crashlytics_test.dart', + 'lineNumber': '99999', + } + ] + }; + + group('$MethodChannelFirebaseCrashlytics', () { + setUpAll(() async { + FirebaseApp app = await Firebase.initializeApp(); + + handleMethodCall((call) async { + logger.add(call); + + if (mockExceptionThrown) { + throw Exception(); + } else if (mockPlatformExceptionThrown) { + throw PlatformException(code: 'UNKNOWN'); + } + + switch (call.method) { + case 'Crashlytics#recordError': + return null; + case 'Crashlytics#checkForUnsentReports': + return {'unsentReports': kUnsentReports}; + case 'Crashlytics#crash': + return null; + case 'Crashlytics#deleteUnsentReports': + kUnsentReports = false; + return null; + case 'Crashlytics#didCrashOnPreviousExecution': + return {'didCrashOnPreviousExecution': true}; + case 'Crashlytics#log': + return null; + case 'Crashlytics#sendUnsentReports': + return null; + case 'Crashlytics#setCrashlyticsCollectionEnabled': + return {'isCrashlyticsCollectionEnabled': true}; + default: + return true; + } + }); + + crashlytics = MethodChannelFirebaseCrashlytics(app: app); + }); + + setUp(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + logger.clear(); + }); + + tearDown(() async { + mockPlatformExceptionThrown = false; + mockExceptionThrown = false; + }); + + group('checkForUnsentReports', () { + test('should call delegate method successfully', () async { + kUnsentReports = true; + var isUnsentReports = await crashlytics.checkForUnsentReports(); + + expect(isUnsentReports, isTrue); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#checkForUnsentReports', + arguments: null, + ), + ]); + + kUnsentReports = false; + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.checkForUnsentReports(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('crash', () { + test('should call delegate method successfully', () { + crashlytics.crash(); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#crash', + arguments: null, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.crash(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('deleteUnsentReports', () { + test('should call delegate method successfully', () async { + kUnsentReports = true; + await crashlytics.deleteUnsentReports(); + + expect(kUnsentReports, isFalse); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#deleteUnsentReports', + arguments: null, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.deleteUnsentReports(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('didCrashOnPreviousExecution', () { + test('should call delegate method successfully', () async { + var didCrash = await crashlytics.didCrashOnPreviousExecution(); + + expect(didCrash, isTrue); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#didCrashOnPreviousExecution', + arguments: null, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.didCrashOnPreviousExecution(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('recordError', () { + test('should call delegate method successfully', () async { + await crashlytics.recordError( + exception: kMockError['exception'], + context: kMockError['context'], + information: kMockError['information'], + stackTraceElements: kMockError['stackTraceElements']); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#recordError', + arguments: { + 'exception': kMockError['exception'], + 'context': kMockError['context'], + 'information': kMockError['information'], + 'stackTraceElements': kMockError['stackTraceElements'], + }, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.recordError(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + test('log', () async { + await crashlytics.log(kMockMessage); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#log', + arguments: { + 'message': kMockMessage, + }, + ), + ]); + }); + + group('sendUnsentReports', () { + test('should call delegate method successfully', () async { + await crashlytics.sendUnsentReports(); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#sendUnsentReports', + arguments: null, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.sendUnsentReports(); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('setCrashlyticsCollectionEnabled', () { + test('should call delegate method successfully', () async { + await crashlytics.setCrashlyticsCollectionEnabled(true); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#setCrashlyticsCollectionEnabled', + arguments: { + 'enabled': true, + }, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = + () => crashlytics.setCrashlyticsCollectionEnabled(true); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('setUserIdentifier', () { + test('should call delegate method successfully', () async { + await crashlytics.setUserIdentifier(kMockUserIdentifier); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#setUserIdentifier', + arguments: { + 'identifier': kMockUserIdentifier, + }, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = + () => crashlytics.setUserIdentifier(kMockUserIdentifier); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + + group('setCustomKey', () { + test('setCustomKey', () async { + await crashlytics.setCustomKey('foo', 'bar'); + + // check native method was called + expect(logger, [ + isMethodCall( + 'Crashlytics#setCustomKey', + arguments: { + 'key': 'foo', + 'value': 'bar', + }, + ), + ]); + }); + + test( + 'catch a [PlatformException] error and throws a [FirebaseCrashlyticsException] error', + () async { + mockPlatformExceptionThrown = true; + + Function callMethod = () => crashlytics.setCustomKey('foo', 'bar'); + await testExceptionHandling('PLATFORM', callMethod); + }); + }); + }); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart new file mode 100644 index 000000000000..0c40c8e7041a --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/method_channel_tests/utils_tests/exception_test.dart @@ -0,0 +1,38 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_crashlytics_platform_interface/src/method_channel/utils/exception.dart'; +import 'package:flutter/services.dart'; + +void main() { + group('catchPlatformException()', () { + test('should throw any exception', () async { + AssertionError assertionError = AssertionError(); + + try { + await catchPlatformException(assertionError); + } on FirebaseException catch (_) { + fail('should have thrown the original exception'); + } catch (_) { + return; + } + fail('should have thrown an exception'); + }); + + test('should catch a [PlatformException] and throw a [FirebaseException]', + () async { + PlatformException platformException = PlatformException(code: 'UNKNOWN'); + try { + await catchPlatformException(platformException); + } on FirebaseException catch (_) { + return; + } catch (_) { + fail('should have thrown an FirebaseCrashlyticsException'); + } + fail('should have thrown an exception'); + }); + }); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/mock.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/mock.dart new file mode 100644 index 000000000000..2d96c4653cc5 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/mock.dart @@ -0,0 +1,76 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_crashlytics_platform_interface/src/method_channel/method_channel_crashlytics.dart'; + +typedef MethodCallCallback = dynamic Function(MethodCall methodCall); +typedef Callback(MethodCall call); + +setupFirebaseCrashlyticsMocks([Callback customHandlers]) { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': { + '[DEFAULT]': { + 'plugins.flutter.io/firebase_crashlytics': { + 'isCrashlyticsCollectionEnabled': true + } + } + }, + } + ]; + } + + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': { + 'plugins.flutter.io/firebase_crashlytics': { + 'isCrashlyticsCollectionEnabled': true + } + }, + }; + } + + if (customHandlers != null) { + customHandlers(call); + } + + return null; + }); +} + +void handleMethodCall(MethodCallCallback methodCallCallback) => + MethodChannelFirebaseCrashlytics.channel + .setMockMethodCallHandler((call) async { + return await methodCallCallback(call); + }); + +Future testExceptionHandling(String type, Function testMethod) async { + try { + await testMethod(); + } on FirebaseException catch (_) { + if (type == 'PLATFORM' || type == 'EXCEPTION') { + return; + } + fail( + 'testExceptionHandling: ${testMethod} threw unexpected FirebaseException'); + } catch (e) { + fail('testExceptionHandling: ${testMethod} threw invalid exception ${e}'); + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/platform_interface_tests/platform_interface_crashlytics_test.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/platform_interface_tests/platform_interface_crashlytics_test.dart new file mode 100644 index 000000000000..9b20906eb8af --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/platform_interface_tests/platform_interface_crashlytics_test.dart @@ -0,0 +1,173 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_crashlytics_platform_interface/firebase_crashlytics_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebaseCrashlyticsMocks(); + + TestFirebaseCrashlyticsPlatform firebaseCrashlyticsPlatform; + + FirebaseApp app; + FirebaseApp secondaryApp; + + group('$FirebaseCrashlyticsPlatform()', () { + setUpAll(() async { + app = await Firebase.initializeApp(); + secondaryApp = await Firebase.initializeApp( + name: 'testApp2', + options: const FirebaseOptions( + appId: '1:1234567890:ios:42424242424242', + apiKey: '123', + projectId: '123', + messagingSenderId: '1234567890', + ), + ); + + firebaseCrashlyticsPlatform = TestFirebaseCrashlyticsPlatform( + app, + ); + }); + + test('Constructor', () { + expect(firebaseCrashlyticsPlatform, isA()); + expect(firebaseCrashlyticsPlatform, isA()); + }); + + test('get.instance', () { + expect(FirebaseCrashlyticsPlatform.instance, + isA()); + expect(FirebaseCrashlyticsPlatform.instance.app.name, + equals(defaultFirebaseAppName)); + }); + + group('set.instance', () { + test('sets the current instance', () { + FirebaseCrashlyticsPlatform.instance = + TestFirebaseCrashlyticsPlatform(secondaryApp); + + expect(FirebaseCrashlyticsPlatform.instance, + isA()); + expect( + FirebaseCrashlyticsPlatform.instance.app.name, equals('testApp2')); + }); + + test('throws an [AssertionError] if instance is null', () { + expect(() => FirebaseCrashlyticsPlatform.instance = null, + throwsAssertionError); + }); + }); + + test('throws if .checkForUnsentReports', () { + try { + firebaseCrashlyticsPlatform.checkForUnsentReports(); + } on UnimplementedError catch (e) { + expect(e.message, equals('checkForUnsentReports() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .crash', () { + try { + firebaseCrashlyticsPlatform.crash(); + } on UnimplementedError catch (e) { + expect(e.message, equals('crash() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .deleteUnsentReports', () { + try { + firebaseCrashlyticsPlatform.deleteUnsentReports(); + } on UnimplementedError catch (e) { + expect(e.message, equals('deleteUnsentReports() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .didCrashOnPreviousExecution', () { + try { + firebaseCrashlyticsPlatform.didCrashOnPreviousExecution(); + } on UnimplementedError catch (e) { + expect(e.message, + equals('didCrashOnPreviousExecution() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .recordError', () { + try { + firebaseCrashlyticsPlatform.recordError(); + } on UnimplementedError catch (e) { + expect(e.message, equals('recordError() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .log', () { + try { + firebaseCrashlyticsPlatform.log('foo'); + } on UnimplementedError catch (e) { + expect(e.message, equals('log() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .sendUnsentReports', () { + try { + firebaseCrashlyticsPlatform.sendUnsentReports(); + } on UnimplementedError catch (e) { + expect(e.message, equals('sendUnsentReports() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .setCrashlyticsCollectionEnabled', () { + try { + firebaseCrashlyticsPlatform.setCrashlyticsCollectionEnabled(true); + } on UnimplementedError catch (e) { + expect(e.message, + equals('setCrashlyticsCollectionEnabled() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .setUserIdentifier', () { + try { + firebaseCrashlyticsPlatform.setUserIdentifier('foo'); + } on UnimplementedError catch (e) { + expect(e.message, equals('setUserIdentifier() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if .setCustomKey', () { + try { + firebaseCrashlyticsPlatform.setCustomKey('foo', 'bar'); + } on UnimplementedError catch (e) { + expect(e.message, equals('setCustomKey() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + }); +} + +class TestFirebaseCrashlyticsPlatform extends FirebaseCrashlyticsPlatform { + TestFirebaseCrashlyticsPlatform(FirebaseApp app) : super(appInstance: app); +} diff --git a/packages/firebase_crashlytics/ios/.gitignore b/packages/firebase_crashlytics/ios/.gitignore deleted file mode 100644 index 710ec6cf1c71..000000000000 --- a/packages/firebase_crashlytics/ios/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig diff --git a/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.h b/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.h deleted file mode 100644 index c0b268534401..000000000000 --- a/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@interface FirebaseCrashlyticsPlugin : NSObject -@end diff --git a/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.m deleted file mode 120000 index f6f2adb48d7a..000000000000 --- a/packages/firebase_crashlytics/ios/Classes/FirebaseCrashlyticsPlugin.m +++ /dev/null @@ -1 +0,0 @@ -../../darwin/Classes/FirebaseCrashlyticsPlugin.m \ No newline at end of file diff --git a/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec b/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec deleted file mode 100644 index eb42d90940ad..000000000000 --- a/packages/firebase_crashlytics/ios/firebase_crashlytics.podspec +++ /dev/null @@ -1,30 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# - -require 'yaml' -pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) -libraryVersion = pubspec['version'].gsub('+', '-') - -Pod::Spec.new do |s| - s.name = 'firebase_crashlytics' - s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' - s.description = <<-DESC -A new flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' - s.ios.deployment_target = '8.0' - s.static_framework = true - s.dependency 'Flutter' - s.dependency 'Fabric' - s.dependency 'Crashlytics' - s.dependency 'Firebase/Core' - - 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 deleted file mode 100644 index a57738b63f43..000000000000 --- a/packages/firebase_crashlytics/lib/firebase_crashlytics.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -library firebase_crashlytics; - -import 'dart:async'; -import 'dart:collection'; -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:stack_trace/stack_trace.dart'; - -part 'src/firebase_crashlytics.dart'; diff --git a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart b/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart deleted file mode 100644 index d37411ef79ad..000000000000 --- a/packages/firebase_crashlytics/lib/src/firebase_crashlytics.dart +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -part of firebase_crashlytics; - -/// The entry point for accessing Crashlytics. -/// -/// You can get an instance by calling `Crashlytics.instance`. -class Crashlytics { - static final Crashlytics instance = Crashlytics(); - - /// Set to true to have errors sent to Crashlytics while in debug mode. By - /// default this is false. - bool 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; - - @visibleForTesting - static const MethodChannel channel = - MethodChannel('plugins.flutter.io/firebase_crashlytics'); - - /// Submits report of a non-fatal error caught by the Flutter framework. - /// to Firebase Crashlytics. - Future recordFlutterError(FlutterErrorDetails details) async { - print('Flutter error caught by Crashlytics plugin:'); - // Since multiple errors can be caught during a single session, we set - // forceReport=true. - FlutterError.dumpErrorToConsole(details, forceReport: true); - - _recordError(details.exceptionAsString(), details.stack, - context: details.context, - information: details.informationCollector == null - ? null - : details.informationCollector(), - printDetails: false); - } - - /// 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 { - print('Error caught by Crashlytics plugin :'); - - _recordError(exception, stack, context: context); - } - - void crash() { - 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 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}); - } - - /// Specify a user identifier which will be visible in the Crashlytics UI. - /// Please be mindful of end-user's privacy. - Future setUserIdentifier(String identifier) async { - await channel.invokeMethod('Crashlytics#setUserIdentifier', - {'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 = >[]; - for (String line in lines) { - final List lineParts = line.split(RegExp('\\s+')); - try { - final String fileName = lineParts[0]; - final String lineNumber = lineParts[1].contains(":") - ? lineParts[1].substring(0, lineParts[1].indexOf(":")).trim() - : lineParts[1]; - - final Map element = { - 'file': fileName, - 'line': lineNumber, - }; - - // The next section would throw an exception in some cases if there was no stop here. - if (lineParts.length < 3) { - elements.add(element); - continue; - } - - if (lineParts[2].contains(".")) { - final String className = - lineParts[2].substring(0, lineParts[2].indexOf(".")).trim(); - final String methodName = - lineParts[2].substring(lineParts[2].indexOf(".") + 1).trim(); - - element['class'] = className; - element['method'] = methodName; - } else { - element['method'] = lineParts[2]; - } - - elements.add(element); - } catch (e) { - print(e.toString()); - } - } - return elements; - } - - // On top of the default exception components, [information] can be passed as well. - // This allows the developer to get a better understanding of exceptions thrown - // by the Flutter framework. [FlutterErrorDetails] often explain why an exception - // occurred and give useful background information in [FlutterErrorDetails.informationCollector]. - // Crashlytics will log this information in addition to the stack trace. - // If [information] is `null` or empty, it will be ignored. - Future _recordError( - dynamic exception, - StackTrace stack, { - dynamic context, - Iterable information, - bool printDetails, - }) async { - bool inDebugMode = false; - if (!enableInDevMode) { - assert(inDebugMode = true); - } - - printDetails ??= inDebugMode; - - final String _information = (information == null || information.isEmpty) - ? '' - : (StringBuffer()..writeAll(information, '\n')).toString(); - - if (printDetails) { - // If available, give context to the exception. - if (context != null) - print('The following exception was thrown $context:'); - - // Need to print the exception to explain why the exception was thrown. - print(exception); - - // Print information provided by the Flutter framework about the exception. - if (_information.isNotEmpty) print('\n$_information'); - - // Not using Trace.format here to stick to the default stack trace format - // that Flutter developers are used to seeing. - if (stack != null) print('\n$stack'); - } - if (!inDebugMode || enableInDevMode) { - // 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. - stack ??= StackTrace.current ?? StackTrace.fromString(''); - - // Report error. - final List stackTraceLines = - Trace.format(stack).trimRight().split('\n'); - final List> stackTraceElements = - getStackTraceElements(stackTraceLines); - - // The context is a string that "should be in a form that will make sense in - // English when following the word 'thrown'" according to the documentation for - // [FlutterErrorDetails.context]. It is displayed to the user on Crashlytics - // as the "reason", which is forced by iOS, with the "thrown" prefix added. - final String result = await channel - .invokeMethod('Crashlytics#onError', { - 'exception': "${exception.toString()}", - 'context': '$context', - 'information': _information, - 'stackTraceElements': stackTraceElements, - 'logs': _logs.toList(), - 'keys': _prepareKeys(), - }); - - // Print result. - print('firebase_crashlytics: $result'); - } - } -} diff --git a/packages/firebase_crashlytics/macos/.gitignore b/packages/firebase_crashlytics/macos/.gitignore deleted file mode 100644 index 710ec6cf1c71..000000000000 --- a/packages/firebase_crashlytics/macos/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig diff --git a/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.h b/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.h deleted file mode 100644 index 9b6c5a0ce27a..000000000000 --- a/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@interface FirebaseCrashlyticsPlugin : NSObject -@end diff --git a/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.m deleted file mode 120000 index f6f2adb48d7a..000000000000 --- a/packages/firebase_crashlytics/macos/Classes/FirebaseCrashlyticsPlugin.m +++ /dev/null @@ -1 +0,0 @@ -../../darwin/Classes/FirebaseCrashlyticsPlugin.m \ No newline at end of file diff --git a/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec b/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec deleted file mode 100644 index 83eb735d7e80..000000000000 --- a/packages/firebase_crashlytics/macos/firebase_crashlytics.podspec +++ /dev/null @@ -1,30 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# - -require 'yaml' -pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) -libraryVersion = pubspec['version'].gsub('+', '-') - -Pod::Spec.new do |s| - s.name = 'firebase_crashlytics' - s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' - s.description = <<-DESC -A new flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' - s.platform = :osx, '10.11' - s.static_framework = true - s.dependency 'FlutterMacOS' - s.dependency 'Fabric' - s.dependency 'Crashlytics' - s.dependency 'Firebase/Core' - - s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{libraryVersion}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-cls\\\"" } -end diff --git a/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart b/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart deleted file mode 100644 index 59f11e023913..000000000000 --- a/packages/firebase_crashlytics/test/firebase_crashlytics_e2e.dart +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index f63d34620eb8..000000000000 --- a/packages/firebase_crashlytics/test/firebase_crashlytics_test.dart +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$Crashlytics', () { - final List log = []; - - final Crashlytics crashlytics = Crashlytics.instance; - - setUp(() async { - Crashlytics.channel - .setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'Crashlytics#onError': - return 'Error reported to Crashlytics.'; - case 'Crashlytics#isDebuggable': - return true; - case 'Crashlytics#setUserEmail': - return true; - case 'Crashlytics#setUserIdentifier': - return true; - case 'Crashlytics#setUserName': - return true; - case 'Crashlytics#getVersion': - return '0.0.0+1'; - default: - return false; - } - }); - log.clear(); - }); - - test('recordFlutterError', () async { - final FlutterErrorDetails details = FlutterErrorDetails( - exception: 'foo exception', - stack: StackTrace.current, - library: 'foo library', - informationCollector: () => [ - DiagnosticsNode.message('test message'), - DiagnosticsNode.message('second message'), - ], - 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'); - expect(log[0].arguments['context'], "context"); - // Confirm that the stack trace contains current stack. - expect( - 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, [ - isMethodCall('Crashlytics#setUserIdentifier', - arguments: {'identifier': 'foo'}) - ]); - }); - - 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' - ]; - final List> elements = - crashlytics.getStackTraceElements(lines); - expect(elements.length, 1); - expect(elements.first, { - 'class': 'StatefulElement', - 'method': 'build', - 'file': 'package:flutter/src/widgets/framework.dart', - 'line': '3825', - }); - }); - - test('getStackTraceElements without character index', () async { - final List lines = [ - 'package:flutter/src/widgets/framework.dart 3825 StatefulElement.build' - ]; - final List> elements = - crashlytics.getStackTraceElements(lines); - expect(elements.length, 1); - expect(elements.first, { - 'class': 'StatefulElement', - 'method': 'build', - 'file': 'package:flutter/src/widgets/framework.dart', - 'line': '3825', - }); - }); - - test('getStackTraceElements without class', () async { - final List lines = [ - 'package:firebase_crashlytics/test/main.dart 12 main' - ]; - final List> elements = - crashlytics.getStackTraceElements(lines); - expect(elements.length, 1); - expect(elements.first, { - 'method': 'main', - 'file': 'package:firebase_crashlytics/test/main.dart', - 'line': '12', - }); - }); - }); -}