diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 37fa693..4527c37 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,20 +8,14 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" - package-ecosystem: "pub" directory: "/" schedule: - interval: "daily" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-minor", "version-update:semver-patch"] + interval: "weekly" - package-ecosystem: "pub" - directory: "example" + directory: "/example" schedule: - interval: "daily" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-minor", "version-update:semver-patch"] + interval: "weekly" diff --git a/.github/workflows/flutter_build_example.yml b/.github/workflows/flutter_build_example.yml index be5c1c2..44e419e 100644 --- a/.github/workflows/flutter_build_example.yml +++ b/.github/workflows/flutter_build_example.yml @@ -5,19 +5,19 @@ on: pull_request: push: branches: - - main + - '**' jobs: build_android: name: Build example for Android runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: distribution: temurin java-version: 17 @@ -35,6 +35,7 @@ jobs: run: flutter pub get - name: Flutter build appbundle + timeout-minutes: 10 run: | cd example flutter build appbundle --release diff --git a/.github/workflows/flutter_checks.yml b/.github/workflows/flutter_checks.yml index df929c1..ba4248a 100644 --- a/.github/workflows/flutter_checks.yml +++ b/.github/workflows/flutter_checks.yml @@ -5,12 +5,12 @@ on: pull_request: push: branches: - - main + - '**' jobs: check_pub_dependencies: name: Check dependencies - timeout-minutes: 10 + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Checkout repository @@ -26,12 +26,13 @@ jobs: run: flutter config --no-analytics - name: Flutter pub get + timeout-minutes: 10 run: flutter pub get check_formatting: name: Check formatting runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 @@ -49,11 +50,12 @@ jobs: run: flutter pub get - name: Check Dart formatting - run: dart format --line-length 80 --set-exit-if-changed . + timeout-minutes: 10 + run: dart format --set-exit-if-changed . analyze: name: Dart analyze - timeout-minutes: 10 + timeout-minutes: 20 runs-on: ubuntu-latest steps: - name: Checkout repository @@ -72,12 +74,13 @@ jobs: run: flutter pub get - name: Run Dart analyze - uses: invertase/github-action-dart-analyzer@v3.0.0 + timeout-minutes: 10 + run: dart analyze --fatal-infos test: name: Flutter test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 @@ -95,12 +98,13 @@ jobs: run: flutter pub get - name: Flutter test + timeout-minutes: 10 run: flutter test test_example: name: Flutter test example runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 @@ -120,6 +124,7 @@ jobs: flutter pub get - name: Flutter test example + timeout-minutes: 10 run: | cd example flutter test diff --git a/.github/workflows/integration_tests_android.yml b/.github/workflows/integration_tests_android.yml index cb35b2c..45d7083 100644 --- a/.github/workflows/integration_tests_android.yml +++ b/.github/workflows/integration_tests_android.yml @@ -3,13 +3,35 @@ name: Integration Tests (Android) on: workflow_dispatch: pull_request: + paths-ignore: + - '**.md' + - 'LICENSE' + - '.github/dependabot.yml' + - '.github/workflows/flutter_build_example.yml' + - '.github/workflows/flutter_checks.yml' + - '.github/workflows/pub_publish.yml' + - '.github/workflows/pub_publish_dry_run.yml' + - '.github/workflows/pub_score.yml' + - 'analysis_options.yaml' + - 'test/**' + - 'example/analysis_options.yaml' + - 'example/test/**' push: branches: - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + - '**' + paths-ignore: + - '**.md' + - 'LICENSE' + - '.github/dependabot.yml' + - '.github/workflows/flutter_build_example.yml' + - '.github/workflows/flutter_checks.yml' + - '.github/workflows/pub_publish.yml' + - '.github/workflows/pub_publish_dry_run.yml' + - '.github/workflows/pub_score.yml' + - 'analysis_options.yaml' + - 'test/**' + - 'example/analysis_options.yaml' + - 'example/test/**' jobs: integration_test: @@ -27,7 +49,7 @@ jobs: uses: actions/checkout@v7 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/pub_publish.yml b/.github/workflows/pub_publish.yml index 3a51984..3b0aea4 100644 --- a/.github/workflows/pub_publish.yml +++ b/.github/workflows/pub_publish.yml @@ -1,17 +1,37 @@ name: Pub publish on: - release: - types: [published] + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' jobs: - publish: - name: Pub publish + verify: + name: Verify package runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - name: Checkout repository + uses: actions/checkout@v7 - - name: Publish - uses: k-paxian/dart-package-publisher@v1.6 + - name: Install Flutter + uses: subosito/flutter-action@v2 with: - credentialJson: ${{ secrets.CREDENTIALS }} + channel: stable + cache: true + + - name: Disable analytics + run: flutter config --no-analytics + + - name: Publish dry run + run: dart pub publish --dry-run + + - name: Flutter test + run: flutter test + + publish: + name: Pub publish + needs: verify + permissions: + contents: read + id-token: write + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 diff --git a/.github/workflows/pub_publish_dry_run.yml b/.github/workflows/pub_publish_dry_run.yml index fd2dff7..d5680c6 100644 --- a/.github/workflows/pub_publish_dry_run.yml +++ b/.github/workflows/pub_publish_dry_run.yml @@ -2,19 +2,30 @@ name: Pub publish dry run on: workflow_dispatch: + pull_request: push: branches: - - main + - '**' jobs: publish-dry-run: name: Pub publish dry run runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - name: Checkout repository + uses: actions/checkout@v7 - - name: Publish dry run - uses: k-paxian/dart-package-publisher@v1.6 + - name: Install Flutter + uses: subosito/flutter-action@v2 with: - credentialJson: MockCredentialJson - dryRunOnly: true + channel: stable + cache: true + + - name: Disable analytics + run: flutter config --no-analytics + + - name: Publish dry run + run: dart pub publish --dry-run + + - name: Flutter test + run: flutter test diff --git a/.github/workflows/pub_score.yml b/.github/workflows/pub_score.yml index e0e7372..fcf99c0 100644 --- a/.github/workflows/pub_score.yml +++ b/.github/workflows/pub_score.yml @@ -5,29 +5,27 @@ on: pull_request: push: branches: - - main + - '**' jobs: check-score: name: Check pub score runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - name: Checkout repository + uses: actions/checkout@v7 - - uses: axel-op/dart-package-analyzer@v3 - id: workflow + - name: Install Flutter + uses: subosito/flutter-action@v2 with: - githubToken: ${{ secrets.GITHUB_TOKEN }} + channel: stable + cache: true - - name: Check score - env: - TOTAL: ${{ steps.workflow.outputs.total }} - TOTAL_MAX: ${{ steps.workflow.outputs.total_max }} - run: | - MISSING_POINTS=$(( $TOTAL_MAX - $TOTAL )) - if (( MISSING_POINTS > 0 )) - then - echo Score can be improved! - echo Run pana to find out how. - exit 1 - fi + - name: Disable analytics + run: flutter config --no-analytics + + - name: Install pana + run: dart pub global activate pana + + - name: Check pub score + run: dart pub global run pana --exit-code-threshold 0 . diff --git a/CHANGELOG.md b/CHANGELOG.md index b37e00a..3299eb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.6.0 + +* **Breaking:** Require Flutter 3.47 and Android API 24 or newer. +* Remove the plugin-owned Kotlin Gradle Plugin so host apps can use Android + Gradle Plugin 9's built-in Kotlin support. +* Update the Android example to Android Gradle Plugin 9.2.1 and Gradle 9.7.1. + ## 0.5.2+1 * Reorder README sections. diff --git a/README.md b/README.md index 4eccae2..82f1c48 100644 --- a/README.md +++ b/README.md @@ -68,13 +68,11 @@ You can use persistent identifiers as long as you have a [privacy policy](https: ## Android build compatibility -`android_id` supports Android Gradle Plugin 9 and Flutter's built-in Kotlin mode. -It also keeps working with AGP 8 and Flutter's temporary -`android.builtInKotlin=false` opt-out. +`android_id` 0.6.0 or newer requires Flutter 3.47, Android Gradle Plugin 9's +built-in Kotlin support, and Android API 24 or newer. The plugin does not apply +or provide a separate Kotlin Gradle Plugin. -The plugin no longer provides its own Android Gradle Plugin or Kotlin Gradle -Plugin classpaths. It only applies the Kotlin Gradle Plugin when the host build -still needs it. This fixes errors such as: +This fixes errors such as: ```text The 'org.jetbrains.kotlin.android' plugin is no longer required for Kotlin @@ -94,7 +92,7 @@ If you're experiencing `MissingPluginException`, try these steps in order: flutter run ``` -2. **Check Flutter Version**: Ensure you're using Flutter 3.10.0 or higher +2. **Check Flutter Version**: Ensure you're using Flutter 3.47.0 or higher ```bash flutter --version ``` diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b3034..e95c400 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,8 @@ include: package:flutter_lints/flutter.yaml + +formatter: + page_width: 120 +analyzer: + exclude: + - build/** + - android/** diff --git a/android/build.gradle b/android/build.gradle index 1cdb33c..2353063 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,24 +10,8 @@ allprojects { apply plugin: "com.android.library" -def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int -boolean hasBuiltInKotlinSupport = agpMajor >= 9 - -def builtInKotlinProperty = findProperty('android.builtInKotlin') -boolean isBuiltInKotlinEnabled = builtInKotlinProperty == null || - builtInKotlinProperty.toString().toBoolean() - -boolean shouldApplyKotlinGradlePlugin = !hasBuiltInKotlinSupport || !isBuiltInKotlinEnabled - -if (shouldApplyKotlinGradlePlugin) { - apply plugin: 'kotlin-android' -} - android { - if (project.android.hasProperty("namespace")) { - namespace = "dev.fluttercommunity.android_id" - } - + namespace = "dev.fluttercommunity.android_id" compileSdk = flutter.compileSdkVersion compileOptions { @@ -35,13 +19,8 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - sourceSets { - main.java.srcDirs += "src/main/kotlin" - test.java.srcDirs += "src/test/kotlin" - } - defaultConfig { - minSdk = flutter.minSdkVersion + minSdk = 24 } dependencies { diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index f9b3034..e95c400 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1 +1,8 @@ include: package:flutter_lints/flutter.yaml + +formatter: + page_width: 120 +analyzer: + exclude: + - build/** + - android/** diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 3b5b324..20361af 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true +android.builtInKotlin=true +android.newDsl=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bdc0141..22d1452 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 6464403..8362c7a 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -18,7 +18,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "9.1.1" apply false + id "com.android.application" version "9.2.1" apply false + // Pin a Flutter-compatible Kotlin runtime without applying the legacy plugin. + id "org.jetbrains.kotlin.android" version "2.4.20" apply false } include ":app" diff --git a/example/integration_test/android_id_test.dart b/example/integration_test/android_id_test.dart index 3cb9290..ca794c6 100644 --- a/example/integration_test/android_id_test.dart +++ b/example/integration_test/android_id_test.dart @@ -8,72 +8,48 @@ void main() { group('AndroidId Plugin Integration Tests', () { const androidIdPlugin = AndroidId(); - testWidgets( - 'getId should not throw MissingPluginException on Android', - (WidgetTester tester) async { - // This test verifies that the plugin is properly registered - // and does not throw MissingPluginException when called on Android + testWidgets('getId should not throw MissingPluginException on Android', (WidgetTester tester) async { + // This test verifies that the plugin is properly registered + // and does not throw MissingPluginException when called on Android - String? androidId; + String? androidId; - // Should not throw MissingPluginException - androidId = await androidIdPlugin.getId(); + // Should not throw MissingPluginException + androidId = await androidIdPlugin.getId(); - // On Android, we should get a non-null ID - // On other platforms, we should get null - expect( - androidId, - isNotNull, - reason: 'Android ID should be available on Android devices/emulators', - ); + // On Android, we should get a non-null ID + // On other platforms, we should get null + expect(androidId, isNotNull, reason: 'Android ID should be available on Android devices/emulators'); - // Android ID should be a hex string - expect( - androidId, - matches(RegExp(r'^[0-9a-f]+$')), - reason: 'Android ID should be a hexadecimal string', - ); + // Android formats this 64-bit value without leading zeroes. + expect( + androidId, + matches(RegExp(r'^[0-9a-f]{1,16}$')), + reason: 'Android ID should be a 64-bit hexadecimal string', + ); + }); - // Android ID should be 16 characters (64-bit hex) - expect( - androidId?.length, - equals(16), - reason: 'Android ID should be 16 hex characters (64-bit)', - ); - }, - ); + testWidgets('getId should return consistent value', (WidgetTester tester) async { + // Call getId multiple times and verify it returns the same value + final id1 = await androidIdPlugin.getId(); + final id2 = await androidIdPlugin.getId(); - testWidgets( - 'getId should return consistent value', - (WidgetTester tester) async { - // Call getId multiple times and verify it returns the same value - final id1 = await androidIdPlugin.getId(); - final id2 = await androidIdPlugin.getId(); + expect(id1, equals(id2), reason: 'Android ID should be consistent across multiple calls'); + }); - expect( - id1, - equals(id2), - reason: 'Android ID should be consistent across multiple calls', - ); - }, - ); + testWidgets('getId should work after hot restart', (WidgetTester tester) async { + // This test verifies that the plugin remains registered and functional + // after pumping frames. Note: Hot restart cannot be simulated in integration tests. - testWidgets( - 'getId should work after hot restart', - (WidgetTester tester) async { - // This test verifies that the plugin remains registered and functional - // after pumping frames. Note: Hot restart cannot be simulated in integration tests. + final id = await androidIdPlugin.getId(); + expect(id, isNotNull); - final id = await androidIdPlugin.getId(); - expect(id, isNotNull); + // Pump frames to simulate app lifecycle + await tester.pumpAndSettle(); - // Pump frames to simulate app lifecycle - await tester.pumpAndSettle(); - - // Should still work - final idAfter = await androidIdPlugin.getId(); - expect(idAfter, equals(id)); - }, - ); + // Should still work + final idAfter = await androidIdPlugin.getId(); + expect(idAfter, equals(id)); + }); }); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 155de41..69c4a1e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -47,12 +47,8 @@ class _MyAppState extends State { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Android ID: $_androidId'), - ), + appBar: AppBar(title: const Text('Plugin example app')), + body: Center(child: Text('Android ID: $_androidId')), ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 5d10413..2ac18d7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: path: ".." relative: true source: path - version: "0.5.2+1" + version: "0.6.0" async: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.20" material_color_utilities: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" path: dependency: transitive description: @@ -234,18 +234,18 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" vm_service: dependency: transitive description: @@ -263,5 +263,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=3.10.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.47.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e2ef583..516abf6 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -7,7 +7,8 @@ version: 1.0.0+1 publish_to: 'none' environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ^3.12.0 + flutter: '>=3.47.0' dependencies: flutter: diff --git a/example/test/android_id_example_test.dart b/example/test/android_id_example_test.dart index 6530893..5100a36 100644 --- a/example/test/android_id_example_test.dart +++ b/example/test/android_id_example_test.dart @@ -16,14 +16,14 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, (MethodCall methodCall) async => '42'); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async => '42', + ); }); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); }); testWidgets('Gets and displays Android ID', (WidgetTester tester) async { diff --git a/lib/android_id.dart b/lib/android_id.dart index 760e177..ae46053 100644 --- a/lib/android_id.dart +++ b/lib/android_id.dart @@ -10,8 +10,7 @@ class AndroidId { /// Calls the native method to retrieve the Android ID. Future getId() async { - final isAndroid = - !kIsWeb && defaultTargetPlatform == TargetPlatform.android; + final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; if (!isAndroid) return null; return _methodChannel.invokeMethod('getId'); diff --git a/pubspec.yaml b/pubspec.yaml index 9c7d41c..be9096f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,11 +13,11 @@ topics: - android - native -version: 0.5.2+1 +version: 0.6.0 environment: sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.10.0' + flutter: '>=3.47.0' dependencies: flutter: diff --git a/test/android_id_test.dart b/test/android_id_test.dart index 6ffada6..4c45285 100644 --- a/test/android_id_test.dart +++ b/test/android_id_test.dart @@ -17,8 +17,7 @@ void main() { }); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); }); test('getAndroidId', () async {