๐ฑ device_identifiers
A lightweight, secure Flutter plugin that provides unique device identifiers for Android and iOS platforms. Perfect for analytics, device tracking, session management, and authentication flows.
โจ Features
- ๐ฏ Simple API - Easy-to-use methods, minimal configuration
- ๐ Privacy-Focused - Uses platform-recommended identifiers
- ๐ฑ Device ID - Cross-platform unique identifier
- ๐ข IMEI Support - Android hardware identifier (with permission)
- โก Lightweight - Minimal dependencies, maximum performance
- ๐ก๏ธ Production-Ready - Null-safe, tested, and reliable
- ๐ Easy Integration - Drop-in solution with Flutter best practices
๐ Platform Support
| Platform | Device ID Method | IMEI Support | Requires Permission |
|---|---|---|---|
| Android | Settings.Secure.ANDROID_ID |
โ Yes | Device ID: No IMEI: Yes (READ_PHONE_STATE) |
| iOS | UIDevice.identifierForVendor |
โ No | No |
Identifier Details
Device Identifier
| Platform | Format | Example | Persistence |
|---|---|---|---|
| Android | 64-bit hex string | 9774d56d682e549c |
Per app reinstall |
| iOS | UUID (36 chars) | E621E1F8-C36C-495A-93FC-0C247A3E6E5F |
Per vendor |
IMEI (Android Only)
| Property | Value |
|---|---|
| Format | 15-digit number |
| Example | 123456789012345 |
| Permission | READ_PHONE_STATE |
| Availability | Phones only (not tablets) |
| Persistence | Permanent (hardware ID) |
๐ Getting Started
Installation
Add device_identifiers to your pubspec.yaml:
dependencies:
device_identifiers: ^0.0.1
Then install the package:
flutter pub get
Import
import 'package:device_identifiers/device_identifiers.dart';
๐ก Usage
Basic Examples
Get Device Identifier
import 'package:device_identifiers/device_identifiers.dart';
Future<void> getDeviceId() async {
final String? deviceId = await DeviceIdentifiers.getDeviceIdentifier();
if (deviceId != null) {
print('Device ID: $deviceId');
} else {
print('Unable to get device identifier');
}
}
Get IMEI (Android Only)
import 'package:device_identifiers/device_identifiers.dart';
import 'package:flutter/services.dart';
Future<void> getImei() async {
try {
final String? imei = await DeviceIdentifiers.getImei();
if (imei != null) {
print('IMEI: $imei');
} else {
print('IMEI not available (may be a tablet)');
}
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
print('READ_PHONE_STATE permission required');
// Request permission using permission_handler or similar
}
}
}
Complete Example with Error Handling & Permissions
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:device_identifiers/device_identifiers.dart';
import 'package:permission_handler/permission_handler.dart';
class DeviceInfoScreen extends StatefulWidget {
@override
_DeviceInfoScreenState createState() => _DeviceInfoScreenState();
}
class _DeviceInfoScreenState extends State<DeviceInfoScreen> {
String _deviceId = 'Loading...';
String _imei = 'Not available';
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadDeviceInfo();
}
Future<void> _loadDeviceInfo() async {
// Get Device ID (no permission needed)
String deviceId;
try {
deviceId = await DeviceIdentifiers.getDeviceIdentifier()
?? 'Unknown Device ID';
} on PlatformException catch (e) {
deviceId = 'Failed: ${e.message}';
} catch (e) {
deviceId = 'Error: $e';
}
// Get IMEI (Android only, requires permission)
String imei = 'Not available (iOS or no permission)';
if (Theme.of(context).platform == TargetPlatform.android) {
try {
final String? imeiValue = await DeviceIdentifiers.getImei();
imei = imeiValue ?? 'Not available (tablet?)';
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
imei = 'Permission denied - tap to grant';
} else {
imei = 'Error: ${e.message}';
}
}
}
if (!mounted) return;
setState(() {
_deviceId = deviceId;
_imei = imei;
_isLoading = false;
});
}
Future<void> _requestPermissionAndGetImei() async {
// Request permission
final status = await Permission.phone.request();
if (status.isGranted) {
final imei = await DeviceIdentifiers.getImei();
setState(() => _imei = imei ?? 'IMEI not available');
} else if (status.isPermanentlyDenied) {
// Open app settings
await openAppSettings();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Device Identifiers')),
body: _isLoading
? Center(child: CircularProgressIndicator())
: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Device ID:', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
SelectableText(_deviceId, textAlign: TextAlign.center),
SizedBox(height: 24),
Text('IMEI:', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
SelectableText(_imei, textAlign: TextAlign.center),
if (_imei.contains('Permission denied')) ...[
SizedBox(height: 16),
ElevatedButton(
onPressed: _requestPermissionAndGetImei,
child: Text('Grant Permission'),
),
],
],
),
),
);
}
}
Permission Setup for IMEI (Android)
1. Add permission to AndroidManifest.xml
The plugin automatically includes this, but if needed, ensure your app's android/app/src/main/AndroidManifest.xml has:
<manifest>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
...
</manifest>
2. Request permission at runtime
import 'package:permission_handler/permission_handler.dart';
Future<bool> requestPhonePermission() async {
final status = await Permission.phone.status;
if (status.isGranted) {
return true;
}
final result = await Permission.phone.request();
return result.isGranted;
}
// Use it before getting IMEI
Future<void> safelyGetImei() async {
if (await requestPhonePermission()) {
final imei = await DeviceIdentifiers.getImei();
print('IMEI: $imei');
} else {
print('Permission not granted');
}
}
1. Analytics & Tracking
Future<void> trackUserSession() async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
await analytics.logEvent(
name: 'session_start',
parameters: {
'device_id': deviceId,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
2. Device-Specific Settings
Future<void> saveDeviceSettings() async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
if (deviceId != null) {
await prefs.setString('device_${deviceId}_theme', 'dark');
await prefs.setBool('device_${deviceId}_notifications', true);
}
}
3. Rate Limiting / Anti-Abuse
Future<bool> checkRateLimit() async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
if (deviceId != null) {
final attempts = await database.getLoginAttempts(deviceId);
return attempts < 5; // Max 5 attempts per device
}
return false;
}
4. Device Registration
Future<void> registerDevice(String userId) async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
if (deviceId != null) {
await api.registerDevice(
userId: userId,
deviceId: deviceId,
platform: Platform.isAndroid ? 'android' : 'ios',
registeredAt: DateTime.now(),
);
}
}
๐ Platform-Specific Details
Android
What is ANDROID_ID?
Settings.Secure.ANDROID_ID is a 64-bit number (expressed as a hexadecimal string) that remains consistent for the lifetime of the app on a device.
Characteristics:
- โ Unique per app signing key + user + device combination
- โ Persists across app updates
- โ Different for each user profile on the same device
- โ Changes on app reinstall or factory reset
- โ Different for apps signed with different keys
Example Output:
9774d56d682e549c
When Does It Change?
- App is uninstalled and reinstalled
- Device is factory reset
- User clears app data (on some devices)
- Different signing key is used
iOS
What is identifierForVendor?
UIDevice.identifierForVendor returns a UUID that identifies the device to the app's vendor.
Characteristics:
- โ Same for all apps from the same vendor (based on Bundle ID prefix)
- โ Persists across app updates
- โ Changes when all apps from the vendor are uninstalled
- โ May change after device restore from backup (in some cases)
Example Output:
E621E1F8-C36C-495A-93FC-0C247A3E6E5F
When Does It Change?
- All apps from the vendor are uninstalled
- Device is restored from backup (sometimes)
- iOS major version upgrade (rare)
Vendor Definition:
Apps are from the same vendor if they share the first two components of their Bundle ID:
com.example.app1andcom.example.app2โ Same vendorcom.company.appandorg.company.appโ Different vendors
โ ๏ธ Important Considerations
Privacy & Compliance
โ DO:
- Use for legitimate analytics and app functionality
- Inform users in your privacy policy
- Combine with user consent for tracking (GDPR, CCPA)
- Use for device-specific settings and preferences
- Implement as part of a multi-factor authentication system
โ DON'T:
- Use as a permanent user identifier
- Track users across uninstalls without consent
- Share device IDs with third parties without disclosure
- Rely on it for critical security (it can change)
- Use for cross-app tracking (violates platform policies)
Best Practices
// โ
GOOD: Combined with user ID
class UserSession {
final String userId;
final String? deviceId;
final DateTime timestamp;
Future<void> create() async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
// Store both user ID and device ID
await saveSession(userId, deviceId);
}
}
// โ BAD: Using device ID as primary identifier
class User {
final String? deviceId; // Device ID can change!
Future<void> login() async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
await loginWithDeviceId(deviceId); // Risky!
}
}
๐ ๏ธ Technical Details
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Flutter App (Dart) โ
โ DeviceIdentifiers.getDeviceId() โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Platform Channel โ
โ MethodChannel('device_identifiers')โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ โ
โโโโโโโโโผโโโโโโโ โโโโผโโโโโโโโโโโ
โ Android โ โ iOS โ
โ (Kotlin) โ โ (Swift) โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
Dependencies
dependencies:
flutter: sdk
plugin_platform_interface: ^2.0.0
dev_dependencies:
flutter_test: sdk
flutter_lints: ^6.0.0
Minimum Requirements
- Flutter: >=1.17.0
- Dart: >=3.10.4
- Android: API 16+ (Android 4.1 Jelly Bean)
- iOS: 11.0+
๐งช Testing
Run Tests
flutter test
Example Test
import 'package:flutter_test/flutter_test.dart';
import 'package:device_identifiers/device_identifiers.dart';
void main() {
test('getDeviceIdentifier returns non-null value', () async {
final deviceId = await DeviceIdentifiers.getDeviceIdentifier();
expect(deviceId, isNotNull);
expect(deviceId, isNotEmpty);
});
}
๐ API Reference
Methods
getDeviceIdentifier()
Returns a unique identifier for the device.
Returns: Future<String?>
- Android: 64-bit hex string (e.g.,
"9774d56d682e549c") - iOS: UUID string (e.g.,
"E621E1F8-C36C-495A-93FC-0C247A3E6E5F") - null: If identifier cannot be retrieved
Throws:
PlatformException- If there's a platform-specific error
Example:
final String? deviceId = await DeviceIdentifiers.getDeviceIdentifier();
getImei()
Returns the IMEI (International Mobile Equipment Identity) of the device.
Platform: Android only (returns null on iOS)
Returns: Future<String?>
- Android: 15-digit IMEI string (e.g.,
"123456789012345") - iOS: Always returns
null - null: If IMEI cannot be retrieved (tablets, permission denied, etc.)
Requires:
- Android:
READ_PHONE_STATEpermission (must be requested at runtime for Android 6.0+)
Throws:
PlatformExceptionwith code'PERMISSION_DENIED'- If READ_PHONE_STATE permission not grantedPlatformException- For other platform-specific errors
Example:
try {
final String? imei = await DeviceIdentifiers.getImei();
if (imei != null) {
print('IMEI: $imei');
} else {
print('IMEI not available');
}
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
print('Permission required: ${e.message}');
}
}
Important Notes:
- IMEI is a hardware identifier and doesn't change (unlike Device ID)
- Only available on devices with cellular capability (phones, not tablets)
- Requires runtime permission on Android 6.0+
- Not accessible on iOS due to platform restrictions
- For Android 8.0+: Uses
TelephonyManager.getImei() - For Android < 8.0: Uses
TelephonyManager.getDeviceId()
๐ Troubleshooting
Common Issues
Issue: Returns null on Android
Solution:
// Ensure you're calling from an async context
Future<void> init() async {
await Future.delayed(Duration(milliseconds: 100));
final id = await DeviceIdentifiers.getDeviceIdentifier();
}
Issue: Different IDs on Android emulators
Explanation: Emulators may return the same ID or change IDs between sessions. This is expected behavior. Always test on real devices for production validation.
Issue: iOS returns null
Possible Causes:
- Device restrictions (MDM, parental controls)
- Privacy settings
- Running on simulator in certain conditions
Solution: Always handle null case in your code.
๐ Related Packages
| Package | Description | Platforms |
|---|---|---|
| device_info_plus | Comprehensive device information | All |
| flutter_udid | Alternative device ID solution | Android, iOS |
| platform_device_id | Multi-platform device IDs | All |
๐ค Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/gurkanfikretgunak/device_identifiers.git
cd device_identifiers
flutter pub get
flutter test
flutter analyze
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ฎ Support
- ๐ Report a bug
- ๐ก Request a feature
- ๐ Documentation
- โญ Star on GitHub
๐ Changelog
See CHANGELOG.md for a detailed list of changes.
Made with โค๏ธ by developers, for developers