From c00f2e9c18b2fd42a513920d522d8fb4e00346a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 13:23:13 +0000 Subject: [PATCH 1/2] feat: bump native Fingerprint SDKs to 4.2.x - Android 4.2.0: https://docs.fingerprint.com/docs/changelog-android-sdk#v4-2-0 - iOS 4.2.0: https://docs.fingerprint.com/docs/changelog-ios-sdk#v4-2-0 - No API changes in either SDK. - Android 4.2.0 AAR still lacks the R8 rules from consumer-rules.pro, so keep the stopgap. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018AX93FxN4bzBaCYXfPTEzC --- .changeset/native-sdk-4-2.md | 5 +++++ android/consumer-rules.pro | 2 +- android/fingerprint.gradle | 2 +- ios/fingerprint_flutter/Package.swift | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/native-sdk-4-2.md diff --git a/.changeset/native-sdk-4-2.md b/.changeset/native-sdk-4-2.md new file mode 100644 index 0000000..7694039 --- /dev/null +++ b/.changeset/native-sdk-4-2.md @@ -0,0 +1,5 @@ +--- +"fingerprint_flutter": minor +--- + +Android and iOS: use native Fingerprint SDK 4.2.x. It collects new signals and improves performance. On Android, it also returns an error instead of crashing when SDK initialization fails. diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro index 3cc50ff..3aa7bfe 100644 --- a/android/consumer-rules.pro +++ b/android/consumer-rules.pro @@ -1,4 +1,4 @@ -# Stopgap for gaps in Fingerprint Android SDK 4.1.x consumer rules. Remove once the SDK ships them. +# Stopgap for gaps in Fingerprint Android SDK 4.1.x and 4.2.x consumer rules. Remove once the SDK ships them. # https://developer.android.com/build/shrink-code#configuration-files # The SDK references Play Services location without depending on it. R8 fails with "Missing class". diff --git a/android/fingerprint.gradle b/android/fingerprint.gradle index 09e19e7..a42aaf2 100644 --- a/android/fingerprint.gradle +++ b/android/fingerprint.gradle @@ -1,6 +1,6 @@ // Patch releases only, same as iOS in ios/fingerprint_flutter/Package.swift. // Take each native minor on purpose. -ext.fingerprint_native_sdk_version = "[4.1.0, 4.2.0)" +ext.fingerprint_native_sdk_version = "[4.2.0, 4.3.0)" task printFingerprintNativeSDKVersion { doLast { diff --git a/ios/fingerprint_flutter/Package.swift b/ios/fingerprint_flutter/Package.swift index be0f1c2..cd7b69f 100644 --- a/ios/fingerprint_flutter/Package.swift +++ b/ios/fingerprint_flutter/Package.swift @@ -21,7 +21,7 @@ let package = Package( // pulled from users once a floating range allows it. Take each minor // on purpose. Example: 2.8.0 caused App Store rejections. // https://github.com/fingerprintjs/flutter/issues/92 - .package(url: "https://github.com/fingerprintjs/fingerprint-ios", .upToNextMinor(from: "4.1.0")) + .package(url: "https://github.com/fingerprintjs/fingerprint-ios", .upToNextMinor(from: "4.2.0")) ], targets: [ .target( From 43c470671c111b96311ba2fe92f239f023db0ae3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 13:33:19 +0000 Subject: [PATCH 2/2] test(android): use SDK error types that load on the desktop JVM Android SDK 4.2.0 obfuscates static initializers of other error classes than 4.1.0. Creating ApiKeyRequired on the desktop JVM now throws IllegalAccessError (works on ART). Production code only uses `is` checks, which do not initialize the class, so apps are not affected. Switch tests to ApiKeyNotFound, which loads on both 4.1.0 and 4.2.0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018AX93FxN4bzBaCYXfPTEzC --- .../fingerprint/flutter/FingerprintHostApiImplTest.kt | 10 ++++++---- .../fingerprint/flutter/FingerprintNativeErrorTest.kt | 11 ++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/android/src/test/kotlin/com/fingerprint/flutter/FingerprintHostApiImplTest.kt b/android/src/test/kotlin/com/fingerprint/flutter/FingerprintHostApiImplTest.kt index d3c4c60..8e09095 100644 --- a/android/src/test/kotlin/com/fingerprint/flutter/FingerprintHostApiImplTest.kt +++ b/android/src/test/kotlin/com/fingerprint/flutter/FingerprintHostApiImplTest.kt @@ -1,7 +1,7 @@ package com.fingerprint.flutter import android.content.Context -import com.fingerprint.android.ApiKeyRequired +import com.fingerprint.android.ApiKeyNotFound import com.fingerprint.android.Failed import com.fingerprint.android.Fingerprint import com.fingerprint.android.FingerprintResponse @@ -19,12 +19,14 @@ class FingerprintHostApiImplTest { @Test fun mapsErrorTypesByInstanceNotSimpleName() { val cases = listOf( - ApiKeyRequired("e1", "msg") to "public_api_key_required", + // Some SDK error classes throw IllegalAccessError when created on the + // desktop JVM (works on ART). The SDK obfuscator breaks their static + // initializer, and which classes it breaks changes with each release. + // These load on SDK 4.1.0 and 4.2.0. + ApiKeyNotFound("e1", "msg") to "public_api_key_not_found", Failed("e2", "fail") to "failed", RequestTimeout("e3", "timeout") to "request_read_timeout", NetworkUnavailableError() to "network_error", - // NetworkError is left out: its obfuscated static initializer in SDK 4.1.0 - // throws IllegalAccessError on the desktop JVM (works on ART). ) for ((nativeError, expectedCode) in cases) { val api = hostApi { _, _ -> diff --git a/android/src/test/kotlin/com/fingerprint/flutter/FingerprintNativeErrorTest.kt b/android/src/test/kotlin/com/fingerprint/flutter/FingerprintNativeErrorTest.kt index b4628d3..bdf3bff 100644 --- a/android/src/test/kotlin/com/fingerprint/flutter/FingerprintNativeErrorTest.kt +++ b/android/src/test/kotlin/com/fingerprint/flutter/FingerprintNativeErrorTest.kt @@ -1,24 +1,25 @@ package com.fingerprint.flutter -import com.fingerprint.android.ApiKeyRequired +import com.fingerprint.android.ApiKeyNotFound import com.fingerprint.android.StateNotReady import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test +// Error types here must load on the desktop JVM, see FingerprintHostApiImplTest. class FingerprintNativeErrorTest { @Test fun stripsUnknownEventIdAndDescription() { - val flutterError = toFlutterError(ApiKeyRequired("Unknown", "Unknown")) - assertEquals("public_api_key_required", flutterError.code) + val flutterError = toFlutterError(ApiKeyNotFound("Unknown", "Unknown")) + assertEquals("public_api_key_not_found", flutterError.code) assertNull(flutterError.message) assertNull(flutterError.details) } @Test fun keepsRealEventIdInDetails() { - val flutterError = toFlutterError(ApiKeyRequired("evt-123", "need key")) - assertEquals("public_api_key_required", flutterError.code) + val flutterError = toFlutterError(ApiKeyNotFound("evt-123", "need key")) + assertEquals("public_api_key_not_found", flutterError.code) assertEquals("need key", flutterError.message) assertEquals("evt-123", flutterError.details) }