Unique Identifier
A Flutter plugin for retrieving a device's unique identifier across Android, iOS, Web, macOS, Linux, and Windows platforms.
| Platform | Method |
|---|---|
| 🤖 Android | ANDROID_ID (scoped to app-signing key, user, and device) |
| 🍎 iOS | UIDevice.identifierForVendor |
| 💻 macOS | IOPlatformUUID via IOKit (uses kIOMainPortDefault on macOS 12+, falls back to kIOMasterPortDefault for macOS 11) |
| 🐧 Linux | Reads /etc/machine-id |
| 🪟 Windows | Reads registry value MachineGuid from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography |
| 🌐 Web | Attempts to read UUID from localStorage. If missing/invalid, generates from browser fingerprint. Falls back to random UUID generation. Stores result in localStorage for persistence. |
Features
- Android: Retrieves
ANDROID_ID(64-bit hex string scoped to app-signing key, user, and device) - iOS: Retrieves
identifierForVendor(IDFV unique per vendor) - macOS: Retrieves
IOPlatformUUIDvia IOKit framework - Linux: Reads
/etc/machine-id - Windows: Reads
MachineGuidfrom registry - Web: Generates/persists UUID via
localStoragewith fingerprint fallback - Cross-platform support with a single API
- Privacy-compliant device tracking
- Swift Package Manager support on iOS
Installation
Add the following to your pubspec.yaml:
dependencies:
unique_identifier_3: ^0.1.0
Then run:
flutter pub get
Import the Package
import 'package:unique_identifier_3/unique_identifier_3.dart';
Usage
Basic Example
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:unique_identifier_3/unique_identifier_3.dart';
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _identifier = 'Unknown';
@override
void initState() {
super.initState();
initUniqueIdentifier();
}
Future<void> initUniqueIdentifier() async {
String identifier;
try {
identifier = await UniqueIdentifier.serial;
} on PlatformException {
identifier = 'Failed to get Unique Identifier';
}
if (!mounted) return;
setState(() {
_identifier = identifier;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Unique Identifier Example'),
),
body: Center(
child: Text('Device ID: $_identifier'),
),
),
);
}
}
Platform-Specific Behavior
Android
- Uses
ANDROID_ID, a 64-bit hexadecimal string - Unique to each combination of app-signing key, user, and device
- Value may change after:
- Factory reset
- App-signing key change
iOS
- Uses
identifierForVendor(IDFV) - Unique per vendor across all apps from the same vendor
- Value may change if:
- All apps from the vendor are uninstalled and reinstalled
macOS
- Uses
IOPlatformUUIDvia IOKit framework - Automatically uses
kIOMainPortDefaulton macOS 12+ (no deprecation warning) - Falls back to
kIOMasterPortDefaultfor compatibility with macOS 11 and earlier
Linux
- Reads
/etc/machine-id - Returns the machine ID as stored by the system
Windows
- Reads
MachineGuidfrom registry keyHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography - Returns the system's unique machine identifier
Web
- Attempts to read a UUID from
window.localStorage - If the value is missing or invalid, it tries to generate one based on browser fingerprint
- If fingerprinting fails, generates a new UUID using Dart code
- The newly generated UUID is stored in
localStoragefor future reuse - Ensures the same UUID persists across reloads and sessions
- Users can manually clear the UUID through browser storage settings
Notes
- On iOS and Android, the UUID may change after uninstalling and reinstalling the app
- macOS implementation avoids deprecated APIs on macOS 12+
- On Web, the UUID persists across reloads and sessions but can be cleared by the user
iOS App Tracking Transparency
If your app uses tracking on iOS, consider requesting App Tracking Transparency (ATT) permission. Below is an example using the app_tracking_transparency package:
import 'package:flutter/material.dart';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:url_launcher/url_launcher.dart';
Future<void> initTrackingTransparency(BuildContext context) async {
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
if (status == TrackingStatus.notDetermined || status == TrackingStatus.denied) {
await showCustomTrackingDialog(
context,
'We use tracking to enhance your experience and provide personalized content and ads.',
);
await Future.delayed(const Duration(milliseconds: 1000));
final newStatus = await AppTrackingTransparency.requestTrackingAuthorization();
if (newStatus == TrackingStatus.notDetermined || newStatus == TrackingStatus.denied) {
await showSettingsDialog(
context,
'Please enable tracking in Settings > Privacy & Security > Tracking.',
);
}
}
final uuid = await AppTrackingTransparency.getAdvertisingIdentifier();
if (uuid == '00000000-0000-0000-0000-000000000000') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Unable to track your device")),
);
}
}
Future<void> showCustomTrackingDialog(BuildContext context, String message) async {
return showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Attention'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Continue'),
),
],
),
);
}
Future<void> showSettingsDialog(BuildContext context, String message) async {
return showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Settings Required'),
content: Text(message),
actions: [
TextButton(
onPressed: () async {
const url = 'app-settings:';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
Navigator.pop(context);
},
child: const Text('Settings'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Not Now'),
),
],
),
);
}
Bugs & Feature Requests
Please open an issue on GitHub for bugs or feature requests. Pull requests are welcome!
License
This project is licensed under the MIT License.