Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
be48faa
Initial plan
Copilot Nov 18, 2025
5dd5670
Handle MissingPluginException gracefully by returning null
Copilot Nov 18, 2025
7d84505
Update README with null return value documentation
Copilot Nov 18, 2025
ff6cdd4
Revert MissingPluginException handling changes
Copilot Nov 19, 2025
ae748a0
Add comprehensive troubleshooting and integration tests for MissingPl…
Copilot Nov 19, 2025
21132a8
Format integration test to comply with 80-char line length
Copilot Nov 19, 2025
4fa8d9e
Fix integration test workflow and add test driver
Copilot Nov 19, 2025
5d2c706
Fix formatting and use correct integration test command
Copilot Nov 19, 2025
5640941
dart format
nohli Nov 19, 2025
011003a
pub get
nohli Nov 19, 2025
f872ee1
Update integration test api levels
nohli Nov 19, 2025
d36ed97
Update integration test profile
nohli Nov 19, 2025
7a8af60
Change syntax
nohli Nov 19, 2025
814ed18
Downgrade max api level
nohli Nov 19, 2025
5367bf4
Print available devices
nohli Nov 19, 2025
909770a
Reduce timeout
nohli Nov 19, 2025
e1d4b5b
Remove upload step
nohli Nov 19, 2025
0096d7f
Use matrix for device type
nohli Nov 19, 2025
e391a89
Improve naming
nohli Nov 19, 2025
b579f1b
Remove whitespace
nohli Nov 19, 2025
496a86a
Move troubleshooting to bottom
nohli Nov 19, 2025
3603cb4
Remove device print
nohli Nov 19, 2025
9de1cb8
Update readme
nohli Nov 19, 2025
5f5f532
Update readme
nohli Nov 19, 2025
5321cd1
Bump version to 0.5.1 and update changelog
Copilot Nov 19, 2025
e69d3d3
pub get
nohli Nov 19, 2025
d761f3f
Update readme
nohli Nov 19, 2025
e832c0b
Update changelog
nohli Nov 19, 2025
a8a52ed
Sort parameters, minor refactoring
nohli Nov 19, 2025
a0e0d95
Sort parameters
nohli Nov 19, 2025
f36f842
Add test for non-Android platforms
nohli Nov 19, 2025
caa4f94
Use variable for mock test result
nohli Nov 19, 2025
8f5c4b5
dart format
nohli Nov 19, 2025
dd090db
Add platform to test name
nohli Nov 20, 2025
abbbd66
Update example/integration_test/android_id_test.dart
nohli Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Integration Tests (Android)

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:
integration_test:
name: Integration test on Android API ${{ matrix.api-level }} - ${{ matrix.profile }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
api-level: [24, 26, 28, 30, 32, 34, 35]
profile: [medium_phone]

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Install Flutter
uses: subosito/flutter-action@v2
with:
channel: stable

- name: Disable analytics
run: flutter config --no-analytics

- name: Flutter pub get
run: |
flutter pub get
cd example
flutter pub get

- name: Enable emulator hardware acceleration (KVM)
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Boot emulator and run integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
profile: ${{ matrix.profile }}
arch: x86_64
script: |
cd example && flutter test integration_test
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.5.1

* Add integration tests to verify plugin functionality across Android API levels
* Add unit coverage to ensure the plugin returns `null` on non-Android platforms
* Expand README with comprehensive troubleshooting guidance and defensive error-handling examples

## 0.5.0

* Breaking: Flutter SDK constraint is now >=3.10.0 (Dart 3-only). Apps on older Flutter versions can’t upgrade.
Expand Down
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ const _androidIdPlugin = AndroidId();
final String? androidId = await _androidIdPlugin.getId();
```

### Optional defensive handling

Treat registration/runtime failures as `null` by wrapping the call in a `try`/`catch`:

```dart
const _androidIdPlugin = AndroidId();

String? androidId;
try {
androidId = await _androidIdPlugin.getId();
} on MissingPluginException {
print('Failed to get Android ID: MissingPluginException');
androidId = null;
} on PlatformException catch (e) {
print('Failed to get Android ID: ${e.message}');
androidId = null;
}
```

**Note:** `getId()` returns `null` on non-Android platforms (iOS, Web, etc.). On Android, it throws `MissingPluginException` if the plugin is not properly registered (see more [below](https://pub.dev/packages/android_id#troubleshooting)).

## Important

Please note that on `Android 8` and above, the `Android ID` is not unique per device, but also per signing key the app was built with:
Expand All @@ -33,7 +54,6 @@ The value may change if a factory reset is performed on the device or if an APK

[Stack Overflow explanation](https://stackoverflow.com/a/43393373)


## Google Play

Before using this plugin in your app, make sure to follow Google Play guidelines.
Expand All @@ -45,3 +65,43 @@ For example [here](https://support.google.com/googleplay/android-developer/answe
Use for non-advertising purposes<br>
You can use persistent identifiers as long as you have a [privacy policy](https://support.google.com/googleplay/android-developer/answer/9859455) and handle the data in accordance with the [Developer Distribution Agreement](https://play.google.com/about/developer-distribution-agreement.html#use) and all applicable privacy laws in the areas where you make your app available.
</blockquote>

## Troubleshooting

If you're experiencing `MissingPluginException`, try these steps in order:

1. **Full Rebuild**: Stop the app completely and rebuild from scratch
```bash
flutter clean
flutter pub get
flutter run
```

2. **Check Flutter Version**: Ensure you're using Flutter 3.10.0 or higher
```bash
flutter --version
```

3. **Verify Android Embedding**: Check `android/app/src/main/.../MainActivity.kt` (or `.java`)
- Should import: `io.flutter.embedding.android.FlutterActivity`
- Should NOT import: `io.flutter.app.FlutterActivity` (legacy v1 embedding)

If using v1 embedding, migrate to v2 following [this guide](https://github.com/flutter/flutter/blob/main/docs/platforms/android/Upgrading-pre-1.12-Android-projects.md)

4. **Check Hot Reload**: Plugin registration happens during cold start. If using hot reload/restart:
- Stop the app completely
- Run `flutter run` again (not hot restart)

5. **Invalidate Caches**: For Android Studio/IntelliJ:
- File → Invalidate Caches → Invalidate and Restart

6. **Test on Real Device**: If on emulator, try on a physical Android device

If none of these work:
- Search existing GitHub issues for a similar report and add your details there, or open a new issue if you can't find one.
- Include the following information:
- Flutter version (`flutter --version`)
- Output of `flutter doctor -v`
- Your `pubspec.yaml` dependencies
- Full error stack trace
- Whether it happens in a fresh project (`flutter create test_app`)
79 changes: 79 additions & 0 deletions example/integration_test/android_id_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:android_id/android_id.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

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

String? androidId;

// 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',
);

// Android ID should be a hex string
expect(
androidId,
matches(RegExp(r'^[0-9a-f]+$')),
reason: 'Android ID should be a 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();

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.

final id = await androidIdPlugin.getId();
expect(id, isNotNull);

// Pump frames to simulate app lifecycle
await tester.pumpAndSettle();

// Should still work
final idAfter = await androidIdPlugin.getId();
expect(idAfter, equals(id));
},
);
});
}
57 changes: 56 additions & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.5.0"
version: "0.5.1"
async:
dependency: transitive
description:
Expand Down Expand Up @@ -56,11 +56,24 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
Expand All @@ -74,6 +87,16 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
Expand Down Expand Up @@ -138,6 +161,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
process:
dependency: transitive
description:
name: process
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
url: "https://pub.dev"
source: hosted
version: "5.0.5"
sky_engine:
dependency: transitive
description: flutter
Expand Down Expand Up @@ -175,6 +214,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph:
dependency: transitive
description:
Expand Down Expand Up @@ -207,6 +254,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.2.1"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
2 changes: 2 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ dev_dependencies:
flutter_lints: ^6.0.0
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ topics:
- android
- native

version: 0.5.0
version: 0.5.1

environment:
sdk: '>=3.0.0 <4.0.0'
Expand Down
33 changes: 31 additions & 2 deletions test/android_id_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
const plugin = AndroidId();
const channel = MethodChannel('android_id');
const mockAndroidId = '42';

TestWidgetsFlutterBinding.ensureInitialized();
debugDefaultTargetPlatformOverride = TargetPlatform.android;

setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (methodCall) async => '42');
.setMockMethodCallHandler(channel, (methodCall) async => mockAndroidId);
});

tearDown(() {
Expand All @@ -21,6 +22,34 @@ void main() {
});

test('getAndroidId', () async {
expect(await plugin.getId(), '42');
expect(await plugin.getId(), mockAndroidId);
});

test('returns null on non-Android platforms', () async {
addTearDown(() => debugDefaultTargetPlatformOverride = null);

debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;

expect(await plugin.getId(), isNull);

debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

expect(await plugin.getId(), isNull);

debugDefaultTargetPlatformOverride = TargetPlatform.linux;

expect(await plugin.getId(), isNull);

debugDefaultTargetPlatformOverride = TargetPlatform.macOS;

expect(await plugin.getId(), isNull);

debugDefaultTargetPlatformOverride = TargetPlatform.windows;

expect(await plugin.getId(), isNull);

debugDefaultTargetPlatformOverride = TargetPlatform.android;

expect(await plugin.getId(), mockAndroidId);
});
}
Loading