Skip to content

Upgrade to version 2.0.0 with major changes including Dart SDK floor …#3

Open
khushal-chothani wants to merge 2 commits into
maeltoukap:mainfrom
khushal-chothani:main
Open

Upgrade to version 2.0.0 with major changes including Dart SDK floor …#3
khushal-chothani wants to merge 2 commits into
maeltoukap:mainfrom
khushal-chothani:main

Conversation

@khushal-chothani

Copy link
Copy Markdown

Description

Modernizes persistent_device_id for a publish-ready Android/iOS release while preserving the existing public API:

PersistentDeviceId.getDeviceId()

This PR raises the package to version 2.0.0, updates the SDK/tooling floor, improves Android and iOS native implementations, adds Swift Package Manager support for iOS, refreshes package metadata/docs/tests, and updates the example app.

Key changes include:

  • Raised SDK constraints to Dart ^3.11.0 and Flutter >=3.41.0.
  • Preserved PersistentDeviceId.getDeviceId() while routing it through the platform interface.
  • Removed scaffolded getPlatformVersion code/tests.
  • Updated Android native channel code to match latest Flutter plugin style.
  • Migrated Android Gradle configuration to Kotlin DSL.
  • Updated AndroidX Security Crypto to stable 1.1.0.
  • Improved Android fallback behavior and safe MediaDrm release.
  • Removed legacy Android manifest package= namespace usage.
  • Added iOS Swift Package Manager-friendly structure.
  • Kept CocoaPods support and updated podspec metadata/source paths.
  • Improved iOS Keychain handling with service-scoped storage and legacy ID migration.
  • Bundled PrivacyInfo.xcprivacy for Apple privacy manifest support.
  • Rewrote README to clearly document ID behavior, limitations, platform differences, and persistence guarantees.
  • Updated CHANGELOG for 2.0.0.
  • Refreshed the example app to demonstrate loading, refreshing, copying, null, and error states.
  • Added/updated Dart, Android native, integration, and example widget tests.

Fixes # (issue)

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

How Has This Been Tested?

Tested locally with Flutter stable 3.44.1 and Dart 3.12.1.

The following commands were run successfully:

flutter analyze
flutter test
cd example && flutter analyze
cd example && flutter test
cd example && flutter build apk --debug
cd example && flutter build ios --no-codesign --config-only
cd example/android && ./gradlew testDebugUnitTest

Test coverage includes:

  • Dart API stability for PersistentDeviceId.getDeviceId().

  • Platform interface delegation.

  • Method-channel forwarding.

  • Repeated calls returning the same stable ID in tests.

  • Nullable platform response handling.

  • Android native getDeviceId method handling.

  • Android unknown method handling.

  • Example UI states for loaded, refreshed, null, and error flows.

  • Unit tests

  • Integration tests

  • Manual tests

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Notes

This is a major release because the package SDK/tooling floor was raised.

Only Android and iOS are declared in this PR. Web, macOS, Windows, and Linux are intentionally not added yet because they require separate platform-specific persistence strategies and tests. Web support in particular cannot provide the same security or persistence guarantees as native storage.

…raise, improved iOS Keychain handling, and added Swift Package Manager support. Updated README and documentation for clarity, and added tests for API stability. Removed legacy Android build files and migrated to Kotlin DSL for Gradle configuration.
@khushal-chothani

Copy link
Copy Markdown
Author

You can test this package directly from this branch using a Git dependency:

dependencies:
  persistent_device_id:
    git:
      url: https://github.com/khushal-chothani/persistent_device_id.git
      ref: main

After adding it to your app, run:

flutter pub get

Then use the existing API:

final deviceId = await PersistentDeviceId.getDeviceId();

@jpdup

jpdup commented Jun 4, 2026

Copy link
Copy Markdown

PR Review: persistent_device_id v2.0.0
This is a significant modernization PR that takes the package from v1.1.0 to v2.0.0 while preserving the public API. Here's my review:

✅ Strengths

API Stability: Kept PersistentDeviceId.getDeviceId() as the stable public interface while improving internals through platform interfaces.

Android Modernization:

Migrated from Groovy to Kotlin DSL (build.gradle → build.gradle.kts)
Updated to Kotlin 2.2.20 and Gradle 9.0.1
Removed legacy manifest package= declaration
Improved AndroidX Security Crypto to stable 1.1.0
Better MediaDrm release and fallback handling
Lowered minSdk from 23 to 21
iOS Improvements:

Added Swift Package Manager support with new Package.swift
Maintained CocoaPods compatibility
Improved Keychain scoping (added service parameter for better isolation)
Added legacy migration logic to preserve existing stored IDs
Added PrivacyInfo.xcprivacy for Apple privacy manifest compliance
Updated to Swift 5.9 and iOS 13.0+
Documentation:

Significantly improved README with clearer guarantees and limitations
Honest about what the ID is and isn't (not a proof of identity, etc.)
Updated CHANGELOG for 2.0.0
Testing: Added meaningful tests for both Android and iOS, with proper mock implementations.

Example App: Completely rewrote with better UX (loading states, error handling, copy-to-clipboard, refresh).

⚠️ Minor Concerns

Migration Story: The iOS migration from legacy account-only Keychain to service-scoped storage is smart, but users should be aware this happens transparently on first access after update.

Null Handling: The API allows null returns, which is documented but consumers need to handle this properly. The README clarifies this but real-world code might not.

iOS minSdk Jump: Raised from 12.0 to 13.0 (relatively minor but worth noting for downstream users on older devices).

Android minSdk: Lowering from 23→21 is good, but ensure SecurityCrypto 1.1.0 actually supports API 21 (it should based on official docs).

🔍 Code Quality

Clean Kotlin code with proper null safety
Swift code follows iOS best practices
Good test coverage with realistic scenarios
Proper resource bundling for privacy manifest
Consistent formatting and documentation comments

📋 Verdict

This is a solid, well-executed modernization PR. It respects backward compatibility at the API level while updating all the underlying infrastructure to current best practices. The migration from platform-specific code directly to platform interfaces is a clean architectural improvement.

Recommended Actions Before Merge:

Verify AndroidX SecurityCrypto 1.1.0 compatibility with API 21 in Android docs
Confirm the Keychain migration logic handles edge cases (corrupted data, permission changes)
Add a migration guide in CHANGELOG for users upgrading from 1.x
This is ready for merge pending those minor verifications.

@jpdup jpdup left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See comments 👆

@jpdup jpdup left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved with comments, I also compiled against that PR branch.
Our App build and ran on iOS

@khushal-chothani

Copy link
Copy Markdown
Author

@jpdup @maeltoukap
Thanks for the detailed review and feedback. I’ve gone through your points, and I really appreciate the suggestions.

I have one question regarding the null return behavior.

Returning null from getDeviceId() was kept as expected behavior because there can still be cases where the native method channel fails or the platform side cannot return an ID safely. In those cases, the current API returns null, and consumers are expected to handle it.

Would you be open to expanding this plugin by allowing users to provide or set their own unique fallback ID through a few additional APIs?

For example, instead of changing the current getDeviceId() behavior internally, we could expose something like:

PersistentDeviceId.setCustomDeviceId(String id)
PersistentDeviceId.clearCustomDeviceId()
PersistentDeviceId.getDeviceId()

The reason I am suggesting this separately is that if we expand the current getDeviceId() function to automatically generate a fallback ID when native ID retrieval fails, then internally send/store that fallback ID on the native side, it may change the expected behavior and could also increase the time required to retrieve the device ID in some cases.

So I wanted to check how you would prefer to expand this plugin:

Should we keep getDeviceId() as-is and add explicit APIs for custom/fallback IDs?

Or should the plugin internally handle fallback generation and storage when native ID retrieval fails?

I’m happy to contribute the implementation based on your direction.

@jpdup

jpdup commented Jun 5, 2026

Copy link
Copy Markdown

I think you’re making the right trade-off for backward compatibility. Keeping the existing contract of returning null makes sense for now.

The main decision point, in my view, is the 2.0.0 major release milestone. Once you reach that version, introducing breaking changes becomes much more reasonable, especially if it allows for a cleaner API, better abstractions, and overall codebase improvements. Ultimately, that decision is entirely up to you as the maintainer.

Given that the Flutter/iOS ecosystem is currently undergoing a broader transition toward Swift Package Manager (SPM), I’d lean toward keeping the plugin as minimal and stable as possible in the short term. That approach reduces migration friction and avoids expanding the API surface prematurely.

We can always revisit the contract in a future major release and decide whether automatic fallback generation/storage should become the default behavior once the Flutter/SPM landscape has settled a bit more.

…oved error management. Updated Android and iOS implementations for better persistence, added migration from legacy storage, and refined documentation. Introduced new tests for various scenarios, ensuring stability and reliability across platforms.
@khushal-chothani

Copy link
Copy Markdown
Author

@jpdup @maeltoukap

I’ve addressed the recommended items:

  • Verified AndroidX Security Crypto 1.1.0 supports API 21+. AndroidKeyStore is not used by the library on API 21/22, which is now documented.
  • Hardened Android persistence so generated IDs are returned only after a successful synchronous write.
  • Added migration from app-private preferences to encrypted preferences without changing the existing ID.
  • Made iOS Keychain reads status-aware, including missing, corrupted, and temporarily unavailable states.
  • Preserved readable legacy Keychain IDs even when migration fails.
  • Generated iOS IDs are now returned only after successful Keychain persistence.
  • Kept the existing nullable API and allowed MethodChannel registration errors to remain visible.
  • Added a detailed migration guide for users upgrading from 1.x.
  • Added native tests covering persistence failures, corrupted values, fallback migration, Keychain migration, and unavailable storage.
  • Fixed the Flutter 3.44 Android release-build issue caused by the generated integration_test plugin registrant.

Validation completed:

  • flutter analyze passed
  • Package and example tests passed
  • Android native tests passed
  • iOS SPM Keychain tests passed
  • Debug and release APK builds passed
  • iOS configuration build passed
  • dart pub publish --dry-run passed with only the expected uncommitted-changes warning

No custom ID setter or temporary Dart fallback was added. PersistentDeviceId.getDeviceId() remains the stable public API, and null now clearly means that no durable native identifier could be obtained or persisted.

@khushal-chothani khushal-chothani requested a review from jpdup June 9, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants