📱 device_id_manager

A cross-platform Flutter plugin that generates and persists unique device IDs — even after reinstalling the app. Built for apps that need a reliable, persistent device identifier with zero permissions.

✨ Features

  • 🔒 Persistent across app reinstalls on both iOS and Android
  • 🏷️ Per-app unique IDs on Android (different apps on the same device get different IDs)
  • 🚫 No permissions required
  • 👤 Identified user support via logIn() / logOut()
  • Synchronous access after initialization — no await needed for getters
  • 🛡️ Secure storage — iOS Keychain (AES-256), Android MediaDrm (hardware-backed)
  • 💥 Fail-safe — throws StateError if accessed before initialize()

🧠 How It Works

🍏 iOS

  1. Checks Keychain for an existing ID (user_device_id key)
  2. If not found, generates a UUID v4 and stores it in Keychain
  3. Keychain data persists across app reinstalls

🤖 Android

  1. Gets hardware device ID via MediaDrm API
  2. Combines with app package name: "$mediaDrmId:$packageName"
  3. Applies SHA-256 hash → per-app unique ID
  4. Falls back to UUID v4 if MediaDrm is unavailable (rooted devices, emulators, custom ROMs)

📊 Platform Comparison

🍏 iOS 🤖 Android
Generation UUID v4 MediaDrm + SHA-256
Storage Keychain Hardware (MediaDrm API)
Survives reinstall ✅ Yes ✅ Yes
Per-app unique ✅ Yes (bundle ID scoped) ✅ Yes (package name hashed)
Permissions 🚫 None 🚫 None

📦 Installation

Add to your pubspec.yaml:

dependencies:
  device_id_manager: ^1.0.0

Then run:

flutter pub get

🍏 iOS Setup

Add the following to your ios/Runner/Info.plist if not already present (required by flutter_secure_storage):

<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to protect your data</string>

🤖 Android Setup

Minimum SDK 18 is required. In android/app/build.gradle:

android {
    defaultConfig {
        minSdkVersion 18
    }
}

🛠️ Usage

Basic

import 'package:device_id_manager/device_id_manager.dart';

// Initialize once at app startup
await DeviceIdManager.initialize();

// Access device ID (synchronous, non-null)
String id = DeviceIdManager.deviceId;
print('Device ID: $id');

👤 Identified Users (Login / Logout)

// Log in with a custom user ID
await DeviceIdManager.logIn('user_12345');

print(DeviceIdManager.isAnonymous);  // false
print(DeviceIdManager.appUserId);    // user_12345
print(DeviceIdManager.customUserId); // user_12345

// Log out — reverts to anonymous device ID
await DeviceIdManager.logOut();

print(DeviceIdManager.isAnonymous);  // true
print(DeviceIdManager.appUserId);    // null
print(DeviceIdManager.customUserId); // <device_id>

🔇 Logging

// Disable logs
DeviceIdLogger.setEnabled(false);

// Enable logs (default)
DeviceIdLogger.setEnabled(true);

📚 API Reference

DeviceIdManager

Property / Method Type Description
initialize() Future<void> Must be called before anything else
deviceId String Persistent device ID
isAnonymous bool true if no custom user ID is set
customUserId String App user ID if logged in, otherwise device ID
appUserId String? Custom user ID, null if anonymous
isInitialized bool Whether initialize() has been called
logIn(String) Future<void> Set an identified user ID
logOut() Future<void> Clear user ID, revert to anonymous
clearUserId() Future<void> Clear all stored IDs and reset state
hasUserId() Future<bool> Check if a device ID exists

⚠️ Important: Accessing any getter before initialize() throws a StateError.

🚧 Limitations

  • MediaDrm only available on Android API ≥ 18
  • On some custom or rooted ROMs, MediaDrm may be unreliable
  • Factory reset will remove the iOS Keychain ID
  • On iOS, Keychain-based ID may reset if iCloud Keychain is disabled or device is restored without backup

🔍 Example

Clone the repository and run the example app:

cd example
flutter run

📄 License

MIT License. © 2025

Libraries

device_id_manager