diff --git a/packages/firebase_crashlytics/analysis_options.yaml b/packages/firebase_crashlytics/analysis_options.yaml new file mode 100644 index 000000000000..82b87f7aad6e --- /dev/null +++ b/packages/firebase_crashlytics/analysis_options.yaml @@ -0,0 +1,10 @@ +# Copyright 2025 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# in the LICENSE file. + +include: ../../analysis_options.yaml + +analyzer: + exclude: + - firebase_crashlytics_platform_interface/lib/src/pigeon/messages.pigeon.dart + - firebase_crashlytics_platform_interface/test/pigeon/test_api.dart \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle b/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle index 97476950e971..af68fd7a00a5 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/build.gradle @@ -5,10 +5,16 @@ apply plugin: 'com.android.library' apply from: file("local-config.gradle") buildscript { + ext.kotlin_version = "1.8.22" repositories { google() mavenCentral() } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.4' + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } } rootProject.allprojects { @@ -18,6 +24,8 @@ rootProject.allprojects { } } +apply plugin: 'kotlin-android' + 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?') @@ -44,11 +52,20 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + } + compileOptions { sourceCompatibility project.ext.javaVersion targetCompatibility project.ext.javaVersion } + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + buildFeatures { buildConfig = true } diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.java deleted file mode 100644 index 5649c1936e45..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022, 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. - */ - -package com.google.firebase.crashlytics; - -import android.annotation.SuppressLint; -import com.google.firebase.crashlytics.internal.Logger; -import java.util.List; - -/** @hide */ -public final class FlutterFirebaseCrashlyticsInternal { - private static final String LOADING_UNIT_KEY = "com.crashlytics.flutter.build-id."; - private static final String FLUTTER_BUILD_ID_DEFAULT_KEY = LOADING_UNIT_KEY + 0; - - @SuppressLint("VisibleForTests") - public static void recordFatalException(Throwable throwable) { - if (throwable == null) { - Logger.getLogger().w("A null value was passed to recordFatalException. Ignoring."); - return; - } - FirebaseCrashlytics.getInstance().core.logFatalException(throwable); - } - - @SuppressLint("VisibleForTests") - public static void setFlutterBuildId(String buildId) { - FirebaseCrashlytics.getInstance().core.setInternalKey(FLUTTER_BUILD_ID_DEFAULT_KEY, buildId); - } - - @SuppressLint("VisibleForTests") - public static void setLoadingUnits(List loadingUnits) { - int unit = 0; - for (String loadingUnit : loadingUnits) { - unit++; - FirebaseCrashlytics.getInstance().core.setInternalKey(LOADING_UNIT_KEY + unit, loadingUnit); - } - } - - private FlutterFirebaseCrashlyticsInternal() {} -} 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 deleted file mode 100644 index 8cdba63ee63a..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/Constants.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022, 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. - */ - -package io.flutter.plugins.firebase.crashlytics; - -public class Constants { - public static final String EXCEPTION = "exception"; - public static final String REASON = "reason"; - 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"; - public static final String FATAL = "fatal"; - public static final String BUILD_ID = "buildId"; - public static final String LOADING_UNITS = "loadingUnits"; - public static final String TIMESTAMP = "timestamp"; - public static final String FIREBASE_APPLICATION_EXCEPTION = "_ae"; - public static final String CRASH_EVENT_KEY = "com.firebase.crashlytics.flutter.fatal"; -} 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 deleted file mode 100644 index e27311499d27..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.java +++ /dev/null @@ -1,20 +0,0 @@ -// 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 deleted file mode 100644 index 50e05c5e8e80..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterError.java +++ /dev/null @@ -1,20 +0,0 @@ -// 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/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java deleted file mode 100644 index 0f0d33a39a83..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.java +++ /dev/null @@ -1,21 +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; - -import androidx.annotation.Keep; -import com.google.firebase.components.Component; -import com.google.firebase.components.ComponentRegistrar; -import com.google.firebase.platforminfo.LibraryVersionComponent; -import java.util.Collections; -import java.util.List; - -@Keep -public class FlutterFirebaseAppRegistrar implements ComponentRegistrar { - @Override - public List> getComponents() { - return Collections.>singletonList( - LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION)); - } -} 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 deleted file mode 100644 index e02fe5f6a1a3..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/java/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.java +++ /dev/null @@ -1,469 +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; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import androidx.annotation.NonNull; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.android.gms.tasks.Tasks; -import com.google.firebase.FirebaseApp; -import com.google.firebase.crashlytics.FirebaseCrashlytics; -import com.google.firebase.crashlytics.FlutterFirebaseCrashlyticsInternal; -import com.google.firebase.crashlytics.internal.Logger; -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.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; - - private static final String FIREBASE_CRASHLYTICS_COLLECTION_ENABLED = - "firebase_crashlytics_collection_enabled"; - - 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() { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - final boolean unsentReports = - Tasks.await(FirebaseCrashlytics.getInstance().checkForUnsentReports()); - - taskCompletionSource.setResult( - new HashMap() { - { - put(Constants.UNSENT_REPORTS, unsentReports); - } - }); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private void crash() { - new Handler(Looper.myLooper()) - .postDelayed( - () -> { - throw new FirebaseCrashlyticsTestCrash(); - }, - 50); - } - - private Task deleteUnsentReports() { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseCrashlytics.getInstance().deleteUnsentReports(); - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task> didCrashOnPreviousExecution() { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - final boolean didCrashOnPreviousExecution = - FirebaseCrashlytics.getInstance().didCrashOnPreviousExecution(); - - taskCompletionSource.setResult( - new HashMap() { - { - put(Constants.DID_CRASH_ON_PREVIOUS_EXECUTION, didCrashOnPreviousExecution); - } - }); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task recordError(final Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); - - final String dartExceptionMessage = - (String) Objects.requireNonNull(arguments.get(Constants.EXCEPTION)); - final String reason = (String) arguments.get(Constants.REASON); - final String information = - (String) Objects.requireNonNull(arguments.get(Constants.INFORMATION)); - final boolean fatal = (boolean) Objects.requireNonNull(arguments.get(Constants.FATAL)); - final String buildId = - (String) Objects.requireNonNull(arguments.get(Constants.BUILD_ID)); - @SuppressWarnings("unchecked") - final List loadingUnits = - (List) Objects.requireNonNull(arguments.get(Constants.LOADING_UNITS)); - - if (buildId.length() > 0) { - FlutterFirebaseCrashlyticsInternal.setFlutterBuildId(buildId); - } - - FlutterFirebaseCrashlyticsInternal.setLoadingUnits(loadingUnits); - - Exception exception; - if (reason != null) { - // Set a "reason" (to match iOS) to show where the exception was thrown. - crashlytics.setCustomKey(Constants.FLUTTER_ERROR_REASON, "thrown " + reason); - exception = - new FlutterError(dartExceptionMessage + ". " + "Error thrown " + reason + "."); - } 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); - } - - if (fatal) { - FlutterFirebaseCrashlyticsInternal.recordFatalException(exception); - } else { - crashlytics.recordException(exception); - } - - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task log(final Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String message = (String) Objects.requireNonNull(arguments.get(Constants.MESSAGE)); - FirebaseCrashlytics.getInstance().log(message); - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task sendUnsentReports() { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseCrashlytics.getInstance().sendUnsentReports(); - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task> setCrashlyticsCollectionEnabled( - final Map arguments) { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - Boolean enabled = (Boolean) Objects.requireNonNull(arguments.get(Constants.ENABLED)); - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled); - - taskCompletionSource.setResult( - new HashMap() { - { - put( - Constants.IS_CRASHLYTICS_COLLECTION_ENABLED, - isCrashlyticsCollectionEnabled(FirebaseApp.getInstance())); - } - }); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task setUserIdentifier(final Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String identifier = - (String) Objects.requireNonNull(arguments.get(Constants.IDENTIFIER)); - FirebaseCrashlytics.getInstance().setUserId(identifier); - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task setCustomKey(final Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String key = (String) Objects.requireNonNull(arguments.get(Constants.KEY)); - String value = (String) Objects.requireNonNull(arguments.get(Constants.VALUE)); - FirebaseCrashlytics.getInstance().setCustomKey(key, value); - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @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 { - - Boolean manifestEnabled = - readCrashlyticsDataCollectionEnabledFromManifest(app.getApplicationContext()); - - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(manifestEnabled); - enabled = manifestEnabled; - } - - return enabled; - } - - private static Boolean readCrashlyticsDataCollectionEnabledFromManifest( - Context applicationContext) { - try { - final PackageManager packageManager = applicationContext.getPackageManager(); - if (packageManager != null) { - final ApplicationInfo applicationInfo = - packageManager.getApplicationInfo( - applicationContext.getPackageName(), PackageManager.GET_META_DATA); - if (applicationInfo != null - && applicationInfo.metaData != null - && applicationInfo.metaData.containsKey(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED)) { - return applicationInfo.metaData.getBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED); - } - } - } catch (PackageManager.NameNotFoundException e) { - // This shouldn't happen since it's this app's package, but fall through to default - // if so. - Logger.getLogger().e("Could not read data collection permission from manifest", e); - } - return true; - } - - @Override - public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult( - new HashMap() { - { - if (firebaseApp.getName().equals("[DEFAULT]")) - put( - Constants.IS_CRASHLYTICS_COLLECTION_ENABLED, - isCrashlyticsCollectionEnabled(FirebaseApp.getInstance())); - } - }); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @Override - public Task didReinitializeFirebaseCore() { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } -} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.kt new file mode 100644 index 000000000000..f64ad1d13979 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/com/google/firebase/crashlytics/FlutterFirebaseCrashlyticsInternal.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2025, 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. + */ +package com.google.firebase.crashlytics + +import android.annotation.SuppressLint +import com.google.firebase.crashlytics.internal.Logger + +/** @hide + */ +object FlutterFirebaseCrashlyticsInternal { + private const val LOADING_UNIT_KEY = "com.crashlytics.flutter.build-id." + private const val FLUTTER_BUILD_ID_DEFAULT_KEY = LOADING_UNIT_KEY + 0 + + @SuppressLint("VisibleForTests") + fun recordFatalException(throwable: Throwable?) { + if (throwable == null) { + Logger.getLogger().w("A null value was passed to recordFatalException. Ignoring.") + return + } + FirebaseCrashlytics.getInstance().core.logFatalException(throwable) + } + + @SuppressLint("VisibleForTests") + fun setFlutterBuildId(buildId: String?) { + FirebaseCrashlytics.getInstance().core.setInternalKey(FLUTTER_BUILD_ID_DEFAULT_KEY, buildId) + } + + @SuppressLint("VisibleForTests") + fun setLoadingUnits(loadingUnits: List) { + var unit = 0 + for (loadingUnit in loadingUnits) { + unit++ + FirebaseCrashlytics.getInstance().core.setInternalKey(LOADING_UNIT_KEY + unit, loadingUnit) + } + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/Constants.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/Constants.kt new file mode 100644 index 000000000000..1853c76fcc37 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/Constants.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2025, 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. + */ +package io.flutter.plugins.firebase.crashlytics + +object Constants { + const val EXCEPTION: String = "exception" + const val REASON: String = "reason" + const val INFORMATION: String = "information" + const val STACK_TRACE_ELEMENTS: String = "stackTraceElements" + const val FLUTTER_ERROR_EXCEPTION: String = "flutter_error_exception" + const val FLUTTER_ERROR_REASON: String = "flutter_error_reason" + const val MESSAGE: String = "message" + const val ENABLED: String = "enabled" + const val IDENTIFIER: String = "identifier" + const val KEY: String = "key" + const val VALUE: String = "value" + const val FILE: String = "file" + const val LINE: String = "line" + const val CLASS: String = "class" + const val METHOD: String = "method" + const val DID_CRASH_ON_PREVIOUS_EXECUTION: String = "didCrashOnPreviousExecution" + const val UNSENT_REPORTS: String = "unsentReports" + const val IS_CRASHLYTICS_COLLECTION_ENABLED: String = "isCrashlyticsCollectionEnabled" + const val FATAL: String = "fatal" + const val BUILD_ID: String = "buildId" + const val LOADING_UNITS: String = "loadingUnits" + const val TIMESTAMP: String = "timestamp" + const val FIREBASE_APPLICATION_EXCEPTION: String = "_ae" + const val CRASH_EVENT_KEY: String = "com.firebase.crashlytics.flutter.fatal" +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsFlutterError.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsFlutterError.kt new file mode 100644 index 000000000000..e322641c54d9 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsFlutterError.kt @@ -0,0 +1,16 @@ +// Copyright 2025 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 +class FirebaseCrashlyticsFlutterError internal constructor(message: String?) : Exception(message) diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.kt new file mode 100644 index 000000000000..43465ddb0d06 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FirebaseCrashlyticsTestCrash.kt @@ -0,0 +1,17 @@ +// Copyright 2025 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 +class FirebaseCrashlyticsTestCrash internal constructor() : + RuntimeException("This is a test crash caused by calling .crash() in Dart.") diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.kt new file mode 100644 index 000000000000..3ee6f9230dc1 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseAppRegistrar.kt @@ -0,0 +1,18 @@ +// Copyright 2025 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 +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.platforminfo.LibraryVersionComponent + +@Keep +class FlutterFirebaseAppRegistrar : ComponentRegistrar { + override fun getComponents(): List> { + return listOf>( + LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION) + ) + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.kt new file mode 100644 index 000000000000..2385a3f351e9 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/FlutterFirebaseCrashlyticsPlugin.kt @@ -0,0 +1,393 @@ +// Copyright 2025 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.content.pm.PackageManager +import android.os.Handler +import android.os.Looper +import android.util.Log +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks +import com.google.firebase.FirebaseApp +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.crashlytics.FlutterFirebaseCrashlyticsInternal +import com.google.firebase.crashlytics.internal.Logger +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin +import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry +import java.util.Objects + +/** FlutterFirebaseCrashlyticsPlugin */ +class FlutterFirebaseCrashlyticsPlugin + + : FlutterFirebasePlugin, FlutterPlugin, CrashlyticsHostApi { + private var channel: MethodChannel? = null + private var messenger: BinaryMessenger? = null + + private fun initInstance(messenger: BinaryMessenger) { + val channelName = "plugins.flutter.io/firebase_crashlytics" + channel = MethodChannel(messenger, channelName) + CrashlyticsHostApi.setUp(messenger, this) + FlutterFirebasePluginRegistry.registerPlugin(channelName, this) + this.messenger = messenger + } + + override fun onAttachedToEngine(binding: FlutterPluginBinding) { + initInstance(binding.binaryMessenger) + } + + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { + channel?.setMethodCallHandler(null) + + checkNotNull(messenger) + CrashlyticsHostApi.setUp(messenger!!, null) + + channel = null + messenger = null + } + + private fun crash() { + Handler(Looper.myLooper()!!) + .postDelayed( + { + throw FirebaseCrashlyticsTestCrash() + }, + 50 + ) + } + + /** + * 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 fun generateStackTraceElement(errorElement: Map): StackTraceElement? { + try { + val fileName = errorElement[Constants.FILE] + val lineNumber = errorElement[Constants.LINE] + val className = errorElement[Constants.CLASS] + val methodName = errorElement[Constants.METHOD] + + return StackTraceElement( + className ?: "", + methodName, + fileName, + lineNumber?.toInt() ?: 0 + ) + } catch (e: Exception) { + Log.e(TAG, "Unable to generate stack trace element from Dart error.") + return null + } + } + + private fun getCrashlyticsSharedPrefs(context: Context): SharedPreferences { + 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 fun isCrashlyticsCollectionEnabled(app: FirebaseApp): Boolean { + val enabled: Boolean + val crashlyticsSharedPrefs = + getCrashlyticsSharedPrefs(app.applicationContext) + + if (crashlyticsSharedPrefs.contains(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED)) { + enabled = crashlyticsSharedPrefs.getBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, true) + } else { + val manifestEnabled = + readCrashlyticsDataCollectionEnabledFromManifest(app.applicationContext) + + FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = manifestEnabled + enabled = manifestEnabled + } + + return enabled + } + + override fun getPluginConstantsForFirebaseApp(firebaseApp: FirebaseApp): Task> { + val taskCompletionSource = TaskCompletionSource>() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val result = if (firebaseApp.name == "[DEFAULT]") { + mapOf( + Constants.IS_CRASHLYTICS_COLLECTION_ENABLED to isCrashlyticsCollectionEnabled(FirebaseApp.getInstance()) + ) + } else { + emptyMap() + } + taskCompletionSource.setResult(result) + } catch (e: Exception) { + taskCompletionSource.setException(e) + } + } + + return taskCompletionSource.task + } + + override fun didReinitializeFirebaseCore(): Task { + val taskCompletionSource = TaskCompletionSource() + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + taskCompletionSource.setResult(null) + } catch (e: Exception) { + taskCompletionSource.setException(e) + } + } + + return taskCompletionSource.task + } + + companion object { + const val TAG: String = "FLTFirebaseCrashlytics" + private const val FIREBASE_CRASHLYTICS_COLLECTION_ENABLED = + "firebase_crashlytics_collection_enabled" + + private fun readCrashlyticsDataCollectionEnabledFromManifest( + applicationContext: Context + ): Boolean { + try { + val packageManager = applicationContext.packageManager + if (packageManager != null) { + val applicationInfo = + packageManager.getApplicationInfo( + applicationContext.packageName, PackageManager.GET_META_DATA + ) + if (applicationInfo.metaData != null && applicationInfo.metaData.containsKey( + FIREBASE_CRASHLYTICS_COLLECTION_ENABLED + ) + ) { + return applicationInfo.metaData.getBoolean( + FIREBASE_CRASHLYTICS_COLLECTION_ENABLED + ) + } + } + } catch (e: PackageManager.NameNotFoundException) { + // This shouldn't happen since it's this app's package, but fall through to default + // if so. + Logger.getLogger().e("Could not read data collection permission from manifest", e) + } + return true + } + } + + override fun recordError(arguments: Map, callback: (Result) -> Unit) { + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val crashlytics = FirebaseCrashlytics.getInstance() + + val dartExceptionMessage = + Objects.requireNonNull(arguments[Constants.EXCEPTION]) as String + val reason = + arguments[Constants.REASON] as String? + val information = + Objects.requireNonNull(arguments[Constants.INFORMATION]) as String + val fatal = + Objects.requireNonNull(arguments[Constants.FATAL]) as Boolean + val buildId = + Objects.requireNonNull(arguments[Constants.BUILD_ID]) as String + val loadingUnits = + Objects.requireNonNull(arguments[Constants.LOADING_UNITS]) as List + + if (buildId.isNotEmpty()) { + FlutterFirebaseCrashlyticsInternal.setFlutterBuildId( + buildId + ) + } + + FlutterFirebaseCrashlyticsInternal.setLoadingUnits( + loadingUnits + ) + + val exception: Exception + if (reason != null) { + // Set a "reason" (to match iOS) to show where the exception was thrown. + crashlytics.setCustomKey( + Constants.FLUTTER_ERROR_REASON, + "thrown $reason" + ) + exception = + FirebaseCrashlyticsFlutterError("$dartExceptionMessage. Error thrown $reason.") + } else { + exception = FirebaseCrashlyticsFlutterError(dartExceptionMessage) + } + + crashlytics.setCustomKey( + Constants.FLUTTER_ERROR_EXCEPTION, + dartExceptionMessage + ) + + val elements: MutableList = + ArrayList() + val errorElements = + Objects.requireNonNull(arguments[Constants.STACK_TRACE_ELEMENTS]) as List> + + for (errorElement in errorElements) { + val stackTraceElement = generateStackTraceElement(errorElement) + if (stackTraceElement != null) { + elements.add(stackTraceElement) + } + } + exception.setStackTrace(elements.toTypedArray()) + + // Log information. + if (information.isNotEmpty()) { + crashlytics.log(information) + } + + if (fatal) { + FlutterFirebaseCrashlyticsInternal.recordFatalException( + exception + ) + } else { + crashlytics.recordException(exception) + } + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun setCustomKey(arguments: Map, callback: (Result) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val key = + Objects.requireNonNull(arguments[Constants.KEY]) as String + val value = + Objects.requireNonNull(arguments[Constants.VALUE]) as String + FirebaseCrashlytics.getInstance().setCustomKey(key, value) + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun setUserIdentifier(arguments: Map, callback: (Result) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val identifier = + Objects.requireNonNull(arguments[Constants.IDENTIFIER]) as String + FirebaseCrashlytics.getInstance().setUserId(identifier) + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun log(arguments: Map, callback: (Result) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val message = + Objects.requireNonNull(arguments[Constants.MESSAGE]) as String + FirebaseCrashlytics.getInstance().log(message) + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun setCrashlyticsCollectionEnabled( + arguments: Map, + callback: (Result?>) -> Unit + ) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val enabled = + Objects.requireNonNull(arguments[Constants.ENABLED]) as Boolean + FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = enabled + + callback(Result.success( + + mapOf( + Constants.IS_CRASHLYTICS_COLLECTION_ENABLED to + isCrashlyticsCollectionEnabled(FirebaseApp.getInstance()) + ) + + )) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun checkForUnsentReports(callback: (Result>) -> Unit) { + + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val unsentReports = + Tasks.await( + FirebaseCrashlytics.getInstance().checkForUnsentReports() + ) + + callback(Result.success(mapOf(Constants.UNSENT_REPORTS to unsentReports))) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun sendUnsentReports(callback: (Result) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + FirebaseCrashlytics.getInstance().sendUnsentReports() + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun deleteUnsentReports(callback: (Result) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + FirebaseCrashlytics.getInstance().deleteUnsentReports() + callback(Result.success(Unit)) + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun didCrashOnPreviousExecution(callback: (Result>) -> Unit) { + FlutterFirebasePlugin.cachedThreadPool.execute { + try { + val didCrashOnPreviousExecution = + FirebaseCrashlytics.getInstance().didCrashOnPreviousExecution() + callback(Result.success(mapOf( Constants.DID_CRASH_ON_PREVIOUS_EXECUTION to + didCrashOnPreviousExecution))) + + } catch (e: Exception) { + handleFailure(callback, e) + } + } + } + + override fun crash(callback: (Result) -> Unit) { + crash() + } + + private fun handleFailure( + callback: (Result) -> Unit, + e: Exception + ) { + val message = e.message ?: "An unknown error occurred" + callback(Result.failure(FlutterError("firebase_crashlytics", message, null))) + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/GeneratedAndroidFirebaseCrashlytics.g.kt b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/GeneratedAndroidFirebaseCrashlytics.g.kt new file mode 100644 index 000000000000..88fe46d62668 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/GeneratedAndroidFirebaseCrashlytics.g.kt @@ -0,0 +1,284 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package io.flutter.plugins.firebase.crashlytics + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object GeneratedAndroidFirebaseCrashlyticsPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +private open class GeneratedAndroidFirebaseCrashlyticsPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** + * Represents all methods for the Firebase Crashlytics plugin. + * + * Generated interface from Pigeon that represents a handler of messages from Flutter. + */ +interface CrashlyticsHostApi { + /** Records a non-fatal error. */ + fun recordError(arguments: Map, callback: (Result) -> Unit) + /** Sets a custom key-value pair. */ + fun setCustomKey(arguments: Map, callback: (Result) -> Unit) + /** Sets the user identifier. */ + fun setUserIdentifier(arguments: Map, callback: (Result) -> Unit) + /** Logs a message. */ + fun log(arguments: Map, callback: (Result) -> Unit) + /** Enables/disables automatic data collection. */ + fun setCrashlyticsCollectionEnabled(arguments: Map, callback: (Result?>) -> Unit) + /** Check for unsent reports. */ + fun checkForUnsentReports(callback: (Result>) -> Unit) + /** Send any unsent reports. */ + fun sendUnsentReports(callback: (Result) -> Unit) + /** Delete any unsent reports. */ + fun deleteUnsentReports(callback: (Result) -> Unit) + /** Check if app crashed on previous execution. */ + fun didCrashOnPreviousExecution(callback: (Result>) -> Unit) + /** Force a crash for testing. */ + fun crash(callback: (Result) -> Unit) + + companion object { + /** The codec used by CrashlyticsHostApi. */ + val codec: MessageCodec by lazy { + GeneratedAndroidFirebaseCrashlyticsPigeonCodec() + } + /** Sets up an instance of `CrashlyticsHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: CrashlyticsHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val argumentsArg = args[0] as Map + api.recordError(argumentsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val argumentsArg = args[0] as Map + api.setCustomKey(argumentsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val argumentsArg = args[0] as Map + api.setUserIdentifier(argumentsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val argumentsArg = args[0] as Map + api.log(argumentsArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val argumentsArg = args[0] as Map + api.setCrashlyticsCollectionEnabled(argumentsArg) { result: Result?> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.checkForUnsentReports$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.checkForUnsentReports{ result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.sendUnsentReports$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.sendUnsentReports{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.deleteUnsentReports$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.deleteUnsentReports{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.didCrashOnPreviousExecution$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.didCrashOnPreviousExecution{ result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.crash$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.crash{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseCrashlyticsPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec index fa95ba19c251..860d69bf53fc 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics.podspec @@ -31,10 +31,11 @@ Pod::Spec.new do |s| s.authors = 'The Chromium Authors' s.source = { :path => '.' } - s.source_files = 'firebase_crashlytics/Sources/firebase_crashlytics/**/*.{h,m}' + s.source_files = 'firebase_crashlytics/Sources/firebase_crashlytics/**/*.{swift}' s.public_header_files = 'firebase_crashlytics/Sources/firebase_crashlytics/include/*.h' s.ios.deployment_target = '13.0' + s.swift_version = '5.0' s.dependency 'Flutter' s.dependency 'firebase_core' @@ -43,7 +44,8 @@ Pod::Spec.new do |s| s.static_framework = true s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\\"#{library_version}\\\" LIBRARY_NAME=\\\"flutter-fire-cls\\\"", - 'DEFINES_MODULE' => 'YES' + 'DEFINES_MODULE' => 'YES', + 'SWIFT_OBJC_BRIDGING_HEADER' => 'firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlytics-Bridging-Header.h' } s.user_target_xcconfig = { 'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym' } end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/Constants.swift b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/Constants.swift new file mode 100644 index 000000000000..a23944758b3c --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/Constants.swift @@ -0,0 +1,6 @@ +// Copyright 2025 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. + +// Auto-generated file. Do not edit. +public let versionNumber = "4.3.7" diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FLTFirebaseCrashlyticsPlugin.m b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FLTFirebaseCrashlyticsPlugin.m deleted file mode 100644 index 79cf8414b416..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FLTFirebaseCrashlyticsPlugin.m +++ /dev/null @@ -1,250 +0,0 @@ -// 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 "include/FLTFirebaseCrashlyticsPlugin.h" -#import "include/Crashlytics_Platform.h" -#import "include/ExceptionModel_Platform.h" - -@import FirebaseCrashlytics; - -#if __has_include() -#import -#else -#import -#endif - -NSString *const kFLTFirebaseCrashlyticsChannelName = @"plugins.flutter.io/firebase_crashlytics"; - -// Argument Keys -NSString *const kCrashlyticsArgumentException = @"exception"; -NSString *const kCrashlyticsArgumentInformation = @"information"; -NSString *const kCrashlyticsArgumentStackTraceElements = @"stackTraceElements"; -NSString *const kCrashlyticsArgumentReason = @"reason"; -NSString *const kCrashlyticsArgumentIdentifier = @"identifier"; -NSString *const kCrashlyticsArgumentKey = @"key"; -NSString *const kCrashlyticsArgumentValue = @"value"; -NSString *const kCrashlyticsArgumentFatal = @"fatal"; - -NSString *const kCrashlyticsArgumentFile = @"file"; -NSString *const kCrashlyticsArgumentLine = @"line"; -NSString *const kCrashlyticsArgumentMethod = @"method"; - -NSString *const kCrashlyticsArgumentEnabled = @"enabled"; -NSString *const kCrashlyticsArgumentUnsentReports = @"unsentReports"; -NSString *const kCrashlyticsArgumentDidCrashOnPreviousExecution = @"didCrashOnPreviousExecution"; - -@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]; - [[FIRCrashlytics crashlytics] setDevelopmentPlatformName:@"Flutter"]; - // We can't currently get the Flutter plugin version number, so use -1. - [[FIRCrashlytics crashlytics] setDevelopmentPlatformVersion:@"-1"]; - }); - - 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#didCrashOnPreviousExecution" isEqualToString:call.method]) { - [self didCrashOnPreviousExecutionWithMethodCallResult:methodCallResult]; - } else { - methodCallResult.success(FlutterMethodNotImplemented); - } -} - -#pragma mark - Firebase Crashlytics API - -- (void)recordError:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - NSString *reason = arguments[kCrashlyticsArgumentReason]; - NSString *information = arguments[kCrashlyticsArgumentInformation]; - NSString *dartExceptionMessage = arguments[kCrashlyticsArgumentException]; - NSArray *errorElements = arguments[kCrashlyticsArgumentStackTraceElements]; - BOOL fatal = [arguments[kCrashlyticsArgumentFatal] boolValue]; - - // 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]]; - } - - if (![reason isEqual:[NSNull null]]) { - reason = [NSString stringWithFormat:@"%@. Error thrown %@.", dartExceptionMessage, reason]; - // Log additional custom value to match Android. - [[FIRCrashlytics crashlytics] setCustomValue:[NSString stringWithFormat:@"thrown %@", reason] - forKey:@"flutter_error_reason"]; - } else { - reason = dartExceptionMessage; - } - - if (fatal) { - NSTimeInterval timeInterval = [NSDate date].timeIntervalSince1970; - [[FIRCrashlytics crashlytics] setCustomValue:@(llrint(timeInterval)) - forKey:@"com.firebase.crashlytics.flutter.fatal"]; - } - - // 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; - exception.onDemand = YES; - exception.isFatal = fatal; - if (fatal) { - [[FIRCrashlytics crashlytics] recordOnDemandExceptionModel:exception]; - } else { - [[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 { - NSString *methodName = [errorElement valueForKey:kCrashlyticsArgumentMethod]; - NSString *className = [errorElement valueForKey:@"class"]; - NSString *symbol = [NSString stringWithFormat:@"%@.%@", className, methodName]; - - FIRStackFrame *frame = [FIRStackFrame - stackFrameWithSymbol:symbol - 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/Sources/firebase_crashlytics/FirebaseCrashlytics-Bridging-Header.h b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlytics-Bridging-Header.h new file mode 100644 index 000000000000..bbbbebab5976 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlytics-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "include/Crashlytics_Platform.h" +#import "include/ExceptionModel_Platform.h" \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsMessages.g.swift b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsMessages.g.swift new file mode 100644 index 000000000000..790b2cd00ddb --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsMessages.g.swift @@ -0,0 +1,337 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private class FirebaseCrashlyticsMessagesPigeonCodecReader: FlutterStandardReader {} + +private class FirebaseCrashlyticsMessagesPigeonCodecWriter: FlutterStandardWriter {} + +private class FirebaseCrashlyticsMessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + FirebaseCrashlyticsMessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + FirebaseCrashlyticsMessagesPigeonCodecWriter(data: data) + } +} + +class FirebaseCrashlyticsMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = + FirebaseCrashlyticsMessagesPigeonCodec( + readerWriter: FirebaseCrashlyticsMessagesPigeonCodecReaderWriter() + ) +} + +/// Represents all methods for the Firebase Crashlytics plugin. +/// +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol CrashlyticsHostApi { + /// Records a non-fatal error. + func recordError(arguments: [String: Any?], completion: @escaping (Result) -> Void) + /// Sets a custom key-value pair. + func setCustomKey(arguments: [String: Any?], completion: @escaping (Result) -> Void) + /// Sets the user identifier. + func setUserIdentifier(arguments: [String: Any?], + completion: @escaping (Result) -> Void) + /// Logs a message. + func log(arguments: [String: Any?], completion: @escaping (Result) -> Void) + /// Enables/disables automatic data collection. + func setCrashlyticsCollectionEnabled(arguments: [String: Bool], + completion: @escaping (Result<[String: Bool]?, Error>) + -> Void) + /// Check for unsent reports. + func checkForUnsentReports(completion: @escaping (Result<[String: Any?], Error>) -> Void) + /// Send any unsent reports. + func sendUnsentReports(completion: @escaping (Result) -> Void) + /// Delete any unsent reports. + func deleteUnsentReports(completion: @escaping (Result) -> Void) + /// Check if app crashed on previous execution. + func didCrashOnPreviousExecution(completion: @escaping (Result<[String: Any?], Error>) -> Void) + /// Force a crash for testing. + func crash(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class CrashlyticsHostApiSetup { + static var codec: FlutterStandardMessageCodec { FirebaseCrashlyticsMessagesPigeonCodec.shared } + /// Sets up an instance of `CrashlyticsHostApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: CrashlyticsHostApi?, + messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + /// Records a non-fatal error. + let recordErrorChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + recordErrorChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let argumentsArg = args[0] as! [String: Any?] + api.recordError(arguments: argumentsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + recordErrorChannel.setMessageHandler(nil) + } + /// Sets a custom key-value pair. + let setCustomKeyChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + setCustomKeyChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let argumentsArg = args[0] as! [String: Any?] + api.setCustomKey(arguments: argumentsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + setCustomKeyChannel.setMessageHandler(nil) + } + /// Sets the user identifier. + let setUserIdentifierChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + setUserIdentifierChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let argumentsArg = args[0] as! [String: Any?] + api.setUserIdentifier(arguments: argumentsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + setUserIdentifierChannel.setMessageHandler(nil) + } + /// Logs a message. + let logChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + logChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let argumentsArg = args[0] as! [String: Any?] + api.log(arguments: argumentsArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + logChannel.setMessageHandler(nil) + } + /// Enables/disables automatic data collection. + let setCrashlyticsCollectionEnabledChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + setCrashlyticsCollectionEnabledChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let argumentsArg = args[0] as! [String: Bool] + api.setCrashlyticsCollectionEnabled(arguments: argumentsArg) { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + setCrashlyticsCollectionEnabledChannel.setMessageHandler(nil) + } + /// Check for unsent reports. + let checkForUnsentReportsChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.checkForUnsentReports\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + checkForUnsentReportsChannel.setMessageHandler { _, reply in + api.checkForUnsentReports { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + checkForUnsentReportsChannel.setMessageHandler(nil) + } + /// Send any unsent reports. + let sendUnsentReportsChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.sendUnsentReports\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + sendUnsentReportsChannel.setMessageHandler { _, reply in + api.sendUnsentReports { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + sendUnsentReportsChannel.setMessageHandler(nil) + } + /// Delete any unsent reports. + let deleteUnsentReportsChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.deleteUnsentReports\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + deleteUnsentReportsChannel.setMessageHandler { _, reply in + api.deleteUnsentReports { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + deleteUnsentReportsChannel.setMessageHandler(nil) + } + /// Check if app crashed on previous execution. + let didCrashOnPreviousExecutionChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.didCrashOnPreviousExecution\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + didCrashOnPreviousExecutionChannel.setMessageHandler { _, reply in + api.didCrashOnPreviousExecution { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + didCrashOnPreviousExecutionChannel.setMessageHandler(nil) + } + /// Force a crash for testing. + let crashChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.crash\(channelSuffix)", + binaryMessenger: binaryMessenger, + codec: codec + ) + if let api { + crashChannel.setMessageHandler { _, reply in + api.crash { result in + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + crashChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsPlugin.swift b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsPlugin.swift new file mode 100644 index 000000000000..23c4d1a0d918 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsPlugin.swift @@ -0,0 +1,378 @@ +// 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 FirebaseCrashlytics +import Flutter +import Foundation + +#if canImport(firebase_core) + import firebase_core +#else + import firebase_core_shared +#endif + +// Constants +private let kFLTFirebaseCrashlyticsChannelName = "plugins.flutter.io/firebase_crashlytics" + +// Argument Keys +private let kCrashlyticsArgumentException = "exception" +private let kCrashlyticsArgumentInformation = "information" +private let kCrashlyticsArgumentStackTraceElements = "stackTraceElements" +private let kCrashlyticsArgumentReason = "reason" +private let kCrashlyticsArgumentIdentifier = "identifier" +private let kCrashlyticsArgumentKey = "key" +private let kCrashlyticsArgumentValue = "value" +private let kCrashlyticsArgumentFatal = "fatal" + +private let kCrashlyticsArgumentFile = "file" +private let kCrashlyticsArgumentLine = "line" +private let kCrashlyticsArgumentMethod = "method" + +private let kCrashlyticsArgumentEnabled = "enabled" +private let kCrashlyticsArgumentUnsentReports = "unsentReports" +private let kCrashlyticsArgumentDidCrashOnPreviousExecution = "didCrashOnPreviousExecution" + +@objc(FirebaseCrashlyticsPlugin) +public class FirebaseCrashlyticsPlugin: NSObject, FLTFirebasePluginProtocol, CrashlyticsHostApi { + + private override init() { + super.init() + // Register with the Flutter Firebase plugin registry. + FLTFirebasePluginRegistry.sharedInstance().register(self) + Crashlytics.crashlytics().setValue("Flutter", forKey: "developmentPlatformName") + Crashlytics.crashlytics().setValue("-1", forKey: "developmentPlatformVersion") + + } + + + // MARK: - Singleton + + // Returns a singleton instance of the Firebase Crashlytics plugin. + public static let sharedInstance = FirebaseCrashlyticsPlugin() + + // MARK: - FlutterPlugin + + @objc + public static func register(with registrar: FlutterPluginRegistrar) { + let binaryMessenger: FlutterBinaryMessenger + + #if os(macOS) + binaryMessenger = registrar.messenger + #elseif os(iOS) + binaryMessenger = registrar.messenger() + #endif + CrashlyticsHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: sharedInstance) + } + + func recordError(arguments: [String: Any?], + completion: @escaping (Result) -> Void) { + let reason = arguments[kCrashlyticsArgumentReason] as? String + let information = arguments[kCrashlyticsArgumentInformation] as? String + let dartExceptionMessage = arguments[kCrashlyticsArgumentException] as? String ?? "" + let errorElements = arguments[kCrashlyticsArgumentStackTraceElements] as? [[String: Any]] ?? [] + let fatal = arguments[kCrashlyticsArgumentFatal] as? Bool ?? false + + // Log additional information so it's captured on the Firebase Crashlytics dashboard. + if let info = information, !info.isEmpty { + Crashlytics.crashlytics().log(info) + } + + // Report crash. + var frames = [StackFrame]() + for errorElement in errorElements { + if let frame = generateFrame(errorElement) { + frames.append(frame) + } + } + + var finalReason = "" + if let unwrappedReason = reason { + finalReason = "\(dartExceptionMessage). Error thrown \(unwrappedReason)." + // Log additional custom value to match Android. + Crashlytics.crashlytics().setCustomValue( + "thrown \(unwrappedReason)", + forKey: "flutter_error_reason" + ) + } else { + finalReason = dartExceptionMessage + } + + if fatal { + let timeInterval = Date().timeIntervalSince1970 + Crashlytics.crashlytics().setCustomValue( + Int64(timeInterval), + forKey: "com.firebase.crashlytics.flutter.fatal" + ) + } + + // Log additional custom value to match Android. + Crashlytics.crashlytics().setCustomValue( + dartExceptionMessage, + forKey: "flutter_error_exception" + ) + + let exception = ExceptionModel(name: "FlutterError", reason: finalReason) + exception.stackTrace = frames + exception.onDemand = true + exception.isFatal = fatal + + if fatal { + Crashlytics.crashlytics().record(onDemandExceptionModel: exception) + } else { + Crashlytics.crashlytics().record(exceptionModel: exception) + } + completion(.success(())) + } + + func setCustomKey(arguments: [String: Any?], + completion: @escaping (Result) -> Void) { + guard let key = arguments[kCrashlyticsArgumentKey] as? String, + let value = arguments[kCrashlyticsArgumentValue] as? String else { + completion(.success(())) + return + } + + Crashlytics.crashlytics().setCustomValue(value, forKey: key) + completion(.success(())) + } + + func setUserIdentifier(arguments: [String: Any?], + completion: @escaping (Result) -> Void) { + guard let identifier = arguments[kCrashlyticsArgumentIdentifier] as? String else { + completion(.success(())) + return + } + + Crashlytics.crashlytics().setUserID(identifier) + completion(.success(())) + } + + func log(arguments: [String: Any?], completion: @escaping (Result) -> Void) { + guard let message = arguments["message"] as? String else { + completion(.success(())) + return + } + + Crashlytics.crashlytics().log(message) + completion(.success(())) + } + + func setCrashlyticsCollectionEnabled(arguments: [String: Bool], + completion: @escaping (Result<[String: Bool]?, any Error>) + -> Void) { + guard let enabled = arguments[kCrashlyticsArgumentEnabled] else { + completion(.success(nil)) + return + } + + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(enabled) + + completion(.success([ + "isCrashlyticsCollectionEnabled": Crashlytics.crashlytics() + .isCrashlyticsCollectionEnabled(), + ])) + } + + func checkForUnsentReports(completion: @escaping (Result<[String: Any?], any Error>) -> Void) { + Crashlytics.crashlytics().checkForUnsentReports { unsentReports in + completion(.success([kCrashlyticsArgumentUnsentReports: unsentReports])) + } + } + + func sendUnsentReports(completion: @escaping (Result) -> Void) { + Crashlytics.crashlytics().sendUnsentReports() + completion(.success(())) + } + + func deleteUnsentReports(completion: @escaping (Result) -> Void) { + Crashlytics.crashlytics().deleteUnsentReports() + completion(.success(())) + } + + func didCrashOnPreviousExecution(completion: @escaping (Result<[String: Any?], any Error>) + -> Void) { + let didCrash = Crashlytics.crashlytics().didCrashDuringPreviousExecution() + completion(.success([kCrashlyticsArgumentDidCrashOnPreviousExecution: didCrash])) + } + + func crash(completion: @escaping (Result) -> Void) { + NSException( + name: NSExceptionName("FirebaseCrashlyticsTestCrash"), + reason: "This is a test crash caused by calling .crash() in Dart.", + userInfo: nil + ).raise() + } + + // MARK: - Firebase Crashlytics API + + private func recordError(_ arguments: Any?, + withMethodCallResult result: FLTFirebaseMethodCallResult) { + guard let args = arguments as? [String: Any] else { + result.success(nil) + return + } + + let reason = args[kCrashlyticsArgumentReason] as? String + let information = args[kCrashlyticsArgumentInformation] as? String + let dartExceptionMessage = args[kCrashlyticsArgumentException] as? String ?? "" + let errorElements = args[kCrashlyticsArgumentStackTraceElements] as? [[String: Any]] ?? [] + let fatal = args[kCrashlyticsArgumentFatal] as? Bool ?? false + + // Log additional information so it's captured on the Firebase Crashlytics dashboard. + if let info = information, !info.isEmpty { + Crashlytics.crashlytics().log(info) + } + + // Report crash. + var frames = [StackFrame]() + for errorElement in errorElements { + if let frame = generateFrame(errorElement) { + frames.append(frame) + } + } + + var finalReason = "" + if let unwrappedReason = reason { + finalReason = "\(dartExceptionMessage). Error thrown \(unwrappedReason)." + // Log additional custom value to match Android. + Crashlytics.crashlytics().setCustomValue( + "thrown \(unwrappedReason)", + forKey: "flutter_error_reason" + ) + } else { + finalReason = dartExceptionMessage + } + + if fatal { + let timeInterval = Date().timeIntervalSince1970 + Crashlytics.crashlytics().setCustomValue( + Int64(timeInterval), + forKey: "com.firebase.crashlytics.flutter.fatal" + ) + } + + // Log additional custom value to match Android. + Crashlytics.crashlytics().setCustomValue( + dartExceptionMessage, + forKey: "flutter_error_exception" + ) + + let exception = ExceptionModel(name: "FlutterError", reason: finalReason) + exception.stackTrace = frames + exception.onDemand = true + exception.isFatal = fatal + + if fatal { + Crashlytics.crashlytics().record(onDemandExceptionModel: exception) + } else { + Crashlytics.crashlytics().record(exceptionModel: exception) + } + result.success(nil) + } + + private func setUserIdentifier(_ arguments: Any?, + withMethodCallResult result: FLTFirebaseMethodCallResult) { + guard let args = arguments as? [String: Any], + let identifier = args[kCrashlyticsArgumentIdentifier] as? String else { + result.success(nil) + return + } + + Crashlytics.crashlytics().setUserID(identifier) + result.success(nil) + } + + private func setCustomKey(_ arguments: Any?, + withMethodCallResult result: FLTFirebaseMethodCallResult) { + guard let args = arguments as? [String: Any], + let key = args[kCrashlyticsArgumentKey] as? String, + let value = args[kCrashlyticsArgumentValue] as? String else { + result.success(nil) + return + } + + Crashlytics.crashlytics().setCustomValue(value, forKey: key) + result.success(nil) + } + + private func log(_ arguments: Any?, withMethodCallResult result: FLTFirebaseMethodCallResult) { + guard let args = arguments as? [String: Any], + let message = args["message"] as? String else { + result.success(nil) + return + } + + Crashlytics.crashlytics().log(message) + result.success(nil) + } + + private func setCrashlyticsCollectionEnabled(_ arguments: Any?, + withMethodCallResult result: FLTFirebaseMethodCallResult) { + guard let args = arguments as? [String: Any], + let enabled = args[kCrashlyticsArgumentEnabled] as? Bool else { + result.success(nil) + return + } + + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(enabled) + result.success([ + "isCrashlyticsCollectionEnabled": Crashlytics.crashlytics() + .isCrashlyticsCollectionEnabled, + ]) + } + + private func checkForUnsentReports(withMethodCallResult result: FLTFirebaseMethodCallResult) { + Crashlytics.crashlytics().checkForUnsentReports { unsentReports in + result.success([kCrashlyticsArgumentUnsentReports: unsentReports]) + } + } + + private func sendUnsentReports(withMethodCallResult result: FLTFirebaseMethodCallResult) { + Crashlytics.crashlytics().sendUnsentReports() + result.success(nil) + } + + private func deleteUnsentReports(withMethodCallResult result: FLTFirebaseMethodCallResult) { + Crashlytics.crashlytics().deleteUnsentReports() + result.success(nil) + } + + private func didCrashOnPreviousExecution(withMethodCallResult result: FLTFirebaseMethodCallResult) { + let didCrash = Crashlytics.crashlytics().didCrashDuringPreviousExecution() + result.success([kCrashlyticsArgumentDidCrashOnPreviousExecution: didCrash]) + } + + private func generateFrame(_ errorElement: [String: Any]) -> StackFrame? { + guard let methodName = errorElement[kCrashlyticsArgumentMethod] as? String, + let className = errorElement["class"] as? String, + let file = errorElement[kCrashlyticsArgumentFile] as? String, + let line = errorElement[kCrashlyticsArgumentLine] as? Int else { + return nil + } + + let symbol = "\(className).\(methodName)" + return StackFrame(symbol: symbol, file: file, line: line) + } + + public func didReinitializeFirebaseCore(_ completion: @escaping () -> Void) { + // Not required for this plugin, nothing to cleanup between reloads. + completion() + } + + public func firebaseLibraryName() -> String { + "flutter-fire-cls" + } + + public func firebaseLibraryVersion() -> String { + versionNumber + } + + public func flutterChannelName() -> String { + kFLTFirebaseCrashlyticsChannelName + } + + public func pluginConstants(for firebaseApp: FirebaseApp) -> [AnyHashable: Any] { + ["isCrashlyticsCollectionEnabled": Crashlytics.crashlytics().isCrashlyticsCollectionEnabled] + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/include/FLTFirebaseCrashlyticsPlugin.h b/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/include/FLTFirebaseCrashlyticsPlugin.h deleted file mode 100644 index 80bfe4d89435..000000000000 --- a/packages/firebase_crashlytics/firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/include/FLTFirebaseCrashlyticsPlugin.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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 - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -#import - -#if __has_include() -#import -#else -#import -#endif - -@interface FLTFirebaseCrashlyticsPlugin : FLTFirebasePlugin -@end diff --git a/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml index 507cd844fe64..08c83f998ac3 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics/pubspec.yaml @@ -37,6 +37,6 @@ flutter: package: io.flutter.plugins.firebase.crashlytics pluginClass: FlutterFirebaseCrashlyticsPlugin ios: - pluginClass: FLTFirebaseCrashlyticsPlugin + pluginClass: FirebaseCrashlyticsPlugin macos: - pluginClass: FLTFirebaseCrashlyticsPlugin + pluginClass: FirebaseCrashlyticsPlugin diff --git a/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.cpp b/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.cpp new file mode 100644 index 000000000000..4d6324794ec8 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.cpp @@ -0,0 +1,427 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace firebase_crashlytics_windows { +using flutter::BasicMessageChannel; +using flutter::CustomEncodableValue; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +FlutterError CreateConnectionError(const std::string channel_name) { + return FlutterError( + "channel-error", + "Unable to establish connection on channel: '" + channel_name + "'.", + EncodableValue("")); +} + +PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} + +EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const { + return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); +} + +void PigeonInternalCodecSerializer::WriteValue( + const EncodableValue& value, flutter::ByteStreamWriter* stream) const { + flutter::StandardCodecSerializer::WriteValue(value, stream); +} + +/// The codec used by CrashlyticsHostApi. +const flutter::StandardMessageCodec& CrashlyticsHostApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &PigeonInternalCodecSerializer::GetInstance()); +} + +// Sets up an instance of `CrashlyticsHostApi` to handle messages through the +// `binary_messenger`. +void CrashlyticsHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, + CrashlyticsHostApi* api) { + CrashlyticsHostApi::SetUp(binary_messenger, api, ""); +} + +void CrashlyticsHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, + CrashlyticsHostApi* api, + const std::string& message_channel_suffix) { + const std::string prepended_suffix = + message_channel_suffix.length() > 0 + ? std::string(".") + message_channel_suffix + : ""; + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.recordError" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_arguments_arg = args.at(0); + if (encodable_arguments_arg.IsNull()) { + reply(WrapError("arguments_arg unexpectedly null.")); + return; + } + const auto& arguments_arg = + std::get(encodable_arguments_arg); + api->RecordError(arguments_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.setCustomKey" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_arguments_arg = args.at(0); + if (encodable_arguments_arg.IsNull()) { + reply(WrapError("arguments_arg unexpectedly null.")); + return; + } + const auto& arguments_arg = + std::get(encodable_arguments_arg); + api->SetCustomKey(arguments_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.setUserIdentifier" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_arguments_arg = args.at(0); + if (encodable_arguments_arg.IsNull()) { + reply(WrapError("arguments_arg unexpectedly null.")); + return; + } + const auto& arguments_arg = + std::get(encodable_arguments_arg); + api->SetUserIdentifier( + arguments_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_" + "platform_interface.CrashlyticsHostApi.log" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_arguments_arg = args.at(0); + if (encodable_arguments_arg.IsNull()) { + reply(WrapError("arguments_arg unexpectedly null.")); + return; + } + const auto& arguments_arg = + std::get(encodable_arguments_arg); + api->Log(arguments_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.setCrashlyticsCollectionEnabled" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_arguments_arg = args.at(0); + if (encodable_arguments_arg.IsNull()) { + reply(WrapError("arguments_arg unexpectedly null.")); + return; + } + const auto& arguments_arg = + std::get(encodable_arguments_arg); + api->SetCrashlyticsCollectionEnabled( + arguments_arg, + [reply](ErrorOr>&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + EncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.checkForUnsentReports" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->CheckForUnsentReports( + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.sendUnsentReports" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->SendUnsentReports( + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.deleteUnsentReports" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->DeleteUnsentReports( + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.didCrashOnPreviousExecution" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->DidCrashOnPreviousExecution( + [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_crashlytics_platform_interface." + "CrashlyticsHostApi.crash" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->Crash([reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } +} + +EncodableValue CrashlyticsHostApi::WrapError(std::string_view error_message) { + return EncodableValue( + EncodableList{EncodableValue(std::string(error_message)), + EncodableValue("Error"), EncodableValue()}); +} + +EncodableValue CrashlyticsHostApi::WrapError(const FlutterError& error) { + return EncodableValue(EncodableList{EncodableValue(error.code()), + EncodableValue(error.message()), + error.details()}); +} + +} // namespace firebase_crashlytics_windows diff --git a/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.h b/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.h new file mode 100644 index 000000000000..34bbe7507979 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics/windows/messages.g.h @@ -0,0 +1,139 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_MESSAGES_G_H_ +#define PIGEON_MESSAGES_G_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace firebase_crashlytics_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) : v_(rhs) {} + ErrorOr(const T&& rhs) : v_(std::move(rhs)) {} + ErrorOr(const FlutterError& rhs) : v_(rhs) {} + ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class CrashlyticsHostApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { + public: + PigeonInternalCodecSerializer(); + inline static PigeonInternalCodecSerializer& GetInstance() { + static PigeonInternalCodecSerializer sInstance; + return sInstance; + } + + void WriteValue(const flutter::EncodableValue& value, + flutter::ByteStreamWriter* stream) const override; + + protected: + flutter::EncodableValue ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const override; +}; + +// Represents all methods for the Firebase Crashlytics plugin. +// +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class CrashlyticsHostApi { + public: + CrashlyticsHostApi(const CrashlyticsHostApi&) = delete; + CrashlyticsHostApi& operator=(const CrashlyticsHostApi&) = delete; + virtual ~CrashlyticsHostApi() {} + // Records a non-fatal error. + virtual void RecordError( + const flutter::EncodableMap& arguments, + std::function reply)> result) = 0; + // Sets a custom key-value pair. + virtual void SetCustomKey( + const flutter::EncodableMap& arguments, + std::function reply)> result) = 0; + // Sets the user identifier. + virtual void SetUserIdentifier( + const flutter::EncodableMap& arguments, + std::function reply)> result) = 0; + // Logs a message. + virtual void Log( + const flutter::EncodableMap& arguments, + std::function reply)> result) = 0; + // Enables/disables automatic data collection. + virtual void SetCrashlyticsCollectionEnabled( + const flutter::EncodableMap& arguments, + std::function> reply)> + result) = 0; + // Check for unsent reports. + virtual void CheckForUnsentReports( + std::function reply)> result) = 0; + // Send any unsent reports. + virtual void SendUnsentReports( + std::function reply)> result) = 0; + // Delete any unsent reports. + virtual void DeleteUnsentReports( + std::function reply)> result) = 0; + // Check if app crashed on previous execution. + virtual void DidCrashOnPreviousExecution( + std::function reply)> result) = 0; + // Force a crash for testing. + virtual void Crash( + std::function reply)> result) = 0; + + // The codec used by CrashlyticsHostApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `CrashlyticsHostApi` to handle messages through the + // `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + CrashlyticsHostApi* api); + static void SetUp(flutter::BinaryMessenger* binary_messenger, + CrashlyticsHostApi* api, + const std::string& message_channel_suffix); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + CrashlyticsHostApi() = default; +}; +} // namespace firebase_crashlytics_windows +#endif // PIGEON_MESSAGES_G_H_ 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 index 42a135d45302..36ce1918152d 100644 --- 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 @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics_platform_interface/src/pigeon/messages.pigeon.dart'; import 'package:flutter/services.dart'; import './utils/exception.dart'; @@ -25,6 +26,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { ); bool? _isCrashlyticsCollectionEnabled; + final _api = CrashlyticsHostApi(); @override bool get isCrashlyticsCollectionEnabled { @@ -47,11 +49,9 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { } try { - Map? data = - await channel.invokeMapMethod( - 'Crashlytics#checkForUnsentReports'); + Map? data = await _api.checkForUnsentReports(); - return data!['unsentReports']; + return data['unsentReports']; } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -60,7 +60,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future crash() async { try { - await channel.invokeMethod('Crashlytics#crash'); + await _api.crash(); } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -69,7 +69,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future deleteUnsentReports() async { try { - await channel.invokeMethod('Crashlytics#deleteUnsentReports'); + await _api.deleteUnsentReports(); } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -78,11 +78,9 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future didCrashOnPreviousExecution() async { try { - Map? data = - await channel.invokeMapMethod( - 'Crashlytics#didCrashOnPreviousExecution'); + Map? data = await _api.didCrashOnPreviousExecution(); - return data!['didCrashOnPreviousExecution']; + return data['didCrashOnPreviousExecution']; } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -99,8 +97,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { List>? stackTraceElements, }) async { try { - await channel - .invokeMethod('Crashlytics#recordError', { + await _api.recordError({ 'exception': exception, 'information': information, 'reason': reason, @@ -117,7 +114,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future log(String message) async { try { - await channel.invokeMethod('Crashlytics#log', { + await _api.log({ 'message': message, }); } on PlatformException catch (e, s) { @@ -128,7 +125,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future sendUnsentReports() async { try { - await channel.invokeMethod('Crashlytics#sendUnsentReports'); + await _api.sendUnsentReports(); } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -137,9 +134,8 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future setCrashlyticsCollectionEnabled(bool enabled) async { try { - Map? data = await channel - .invokeMapMethod( - 'Crashlytics#setCrashlyticsCollectionEnabled', { + Map? data = + await _api.setCrashlyticsCollectionEnabled({ 'enabled': enabled, }); @@ -152,8 +148,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future setUserIdentifier(String identifier) async { try { - await channel.invokeMethod( - 'Crashlytics#setUserIdentifier', { + await _api.setUserIdentifier({ 'identifier': identifier, }); } on PlatformException catch (e, s) { @@ -164,8 +159,7 @@ class MethodChannelFirebaseCrashlytics extends FirebaseCrashlyticsPlatform { @override Future setCustomKey(String key, String value) async { try { - await channel - .invokeMethod('Crashlytics#setCustomKey', { + await _api.setCustomKey({ 'key': key, 'value': value, }); diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/pigeon/messages.pigeon.dart new file mode 100644 index 000000000000..3b3ae5f6b5aa --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -0,0 +1,347 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Represents all methods for the Firebase Crashlytics plugin. +class CrashlyticsHostApi { + /// Constructor for [CrashlyticsHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + CrashlyticsHostApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + /// Records a non-fatal error. + Future recordError(Map arguments) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([arguments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets a custom key-value pair. + Future setCustomKey(Map arguments) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([arguments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the user identifier. + Future setUserIdentifier(Map arguments) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([arguments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Logs a message. + Future log(Map arguments) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([arguments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Enables/disables automatic data collection. + Future?> setCrashlyticsCollectionEnabled( + Map arguments) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([arguments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Map?) + ?.cast(); + } + } + + /// Check for unsent reports. + Future> checkForUnsentReports() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.checkForUnsentReports$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Map?)! + .cast(); + } + } + + /// Send any unsent reports. + Future sendUnsentReports() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.sendUnsentReports$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Delete any unsent reports. + Future deleteUnsentReports() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.deleteUnsentReports$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Check if app crashed on previous execution. + Future> didCrashOnPreviousExecution() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.didCrashOnPreviousExecution$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Map?)! + .cast(); + } + } + + /// Force a crash for testing. + Future crash() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.crash$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/copyright.txt b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/copyright.txt new file mode 100644 index 000000000000..4e197781c6db --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2025, 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. \ No newline at end of file diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/messages.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/messages.dart new file mode 100644 index 000000000000..c64f92db8c6f --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pigeons/messages.dart @@ -0,0 +1,69 @@ +// Copyright 2025 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:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/pigeon/messages.pigeon.dart', + dartTestOut: 'test/pigeon/test_api.dart', + dartPackageName: 'firebase_crashlytics_platform_interface', + kotlinOut: + '../firebase_crashlytics/android/src/main/kotlin/io/flutter/plugins/firebase/crashlytics/GeneratedAndroidFirebaseCrashlytics.g.kt', + kotlinOptions: KotlinOptions( + package: 'io.flutter.plugins.firebase.crashlytics', + ), + swiftOut: + '../firebase_crashlytics/ios/firebase_crashlytics/Sources/firebase_crashlytics/FirebaseCrashlyticsMessages.g.swift', + cppHeaderOut: '../firebase_crashlytics/windows/messages.g.h', + cppSourceOut: '../firebase_crashlytics/windows/messages.g.cpp', + cppOptions: CppOptions(namespace: 'firebase_crashlytics_windows'), + copyrightHeader: 'pigeons/copyright.txt', + ), +) + +/// Represents all methods for the Firebase Crashlytics plugin. +@HostApi(dartHostTestHandler: 'TestFirebaseCrashlyticsHostApi') +abstract class CrashlyticsHostApi { + /// Records a non-fatal error. + @async + void recordError(Map arguments); + + /// Sets a custom key-value pair. + @async + void setCustomKey(Map arguments); + + /// Sets the user identifier. + @async + void setUserIdentifier(Map arguments); + + /// Logs a message. + @async + void log(Map arguments); + + /// Enables/disables automatic data collection. + @async + Map? setCrashlyticsCollectionEnabled( + Map arguments); + + /// Check for unsent reports. + @async + Map checkForUnsentReports(); + + /// Send any unsent reports. + @async + void sendUnsentReports(); + + /// Delete any unsent reports. + @async + void deleteUnsentReports(); + + /// Check if app crashed on previous execution. + @async + Map didCrashOnPreviousExecution(); + + /// Force a crash for testing. + @async + void crash(); +} diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml index 8098b3e38904..3f6ead8188ca 100644 --- a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/pubspec.yaml @@ -22,3 +22,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 + pigeon: 25.3.2 diff --git a/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/pigeon/test_api.dart b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/pigeon/test_api.dart new file mode 100644 index 000000000000..39755f754bb1 --- /dev/null +++ b/packages/firebase_crashlytics/firebase_crashlytics_platform_interface/test/pigeon/test_api.dart @@ -0,0 +1,380 @@ +// Copyright 2025, 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. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: avoid_relative_lib_imports +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:firebase_crashlytics_platform_interface/src/pigeon/messages.pigeon.dart'; + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Represents all methods for the Firebase Crashlytics plugin. +abstract class TestFirebaseCrashlyticsHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + /// Records a non-fatal error. + Future recordError(Map arguments); + + /// Sets a custom key-value pair. + Future setCustomKey(Map arguments); + + /// Sets the user identifier. + Future setUserIdentifier(Map arguments); + + /// Logs a message. + Future log(Map arguments); + + /// Enables/disables automatic data collection. + Future?> setCrashlyticsCollectionEnabled( + Map arguments); + + /// Check for unsent reports. + Future> checkForUnsentReports(); + + /// Send any unsent reports. + Future sendUnsentReports(); + + /// Delete any unsent reports. + Future deleteUnsentReports(); + + /// Check if app crashed on previous execution. + Future> didCrashOnPreviousExecution(); + + /// Force a crash for testing. + Future crash(); + + static void setUp( + TestFirebaseCrashlyticsHostApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError was null.'); + final List args = (message as List?)!; + final Map? arg_arguments = + (args[0] as Map?)?.cast(); + assert(arg_arguments != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.recordError was null, expected non-null Map.'); + try { + await api.recordError(arg_arguments!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey was null.'); + final List args = (message as List?)!; + final Map? arg_arguments = + (args[0] as Map?)?.cast(); + assert(arg_arguments != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCustomKey was null, expected non-null Map.'); + try { + await api.setCustomKey(arg_arguments!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier was null.'); + final List args = (message as List?)!; + final Map? arg_arguments = + (args[0] as Map?)?.cast(); + assert(arg_arguments != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setUserIdentifier was null, expected non-null Map.'); + try { + await api.setUserIdentifier(arg_arguments!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log was null.'); + final List args = (message as List?)!; + final Map? arg_arguments = + (args[0] as Map?)?.cast(); + assert(arg_arguments != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.log was null, expected non-null Map.'); + try { + await api.log(arg_arguments!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled was null.'); + final List args = (message as List?)!; + final Map? arg_arguments = + (args[0] as Map?)?.cast(); + assert(arg_arguments != null, + 'Argument for dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.setCrashlyticsCollectionEnabled was null, expected non-null Map.'); + try { + final Map? output = + await api.setCrashlyticsCollectionEnabled(arg_arguments!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.checkForUnsentReports$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + final Map output = + await api.checkForUnsentReports(); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.sendUnsentReports$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + await api.sendUnsentReports(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.deleteUnsentReports$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + await api.deleteUnsentReports(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.didCrashOnPreviousExecution$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + final Map output = + await api.didCrashOnPreviousExecution(); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.firebase_crashlytics_platform_interface.CrashlyticsHostApi.crash$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + try { + await api.crash(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } +}