Retrieving Device Information with device_info_plus
Retrieving Device Information with device_info_plus
Many production Flutter apps need to know what device they are running on — to tailor the user experience, report crashes accurately, enforce platform-specific licensing, or log diagnostics. The device_info_plus package exposes a clean, typed API that surfaces platform-specific metadata such as the device model, OS version, system name, and unique identifiers on both Android and iOS (as well as Web, Windows, macOS, and Linux).
device_info_plus is the community-maintained successor to the original device_info plugin. Always use device_info_plus in new projects — the older package is deprecated and unmaintained.Adding the Dependency
Add device_info_plus to your pubspec.yaml and run flutter pub get:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
device_info_plus: ^10.1.0 # use the latest stable version
No extra Android permissions or iOS Info.plist entries are required for the basic metadata fields covered in this lesson.
Core API — DeviceInfoPlugin
The package's entry point is DeviceInfoPlugin. You create a single instance and call the appropriate platform getter, which returns a typed info object:
- Android —
androidInforeturns anAndroidDeviceInfoobject - iOS —
iosInforeturns anIosDeviceInfoobject - Web —
webBrowserInforeturns aWebBrowserInfoobject - Windows / macOS / Linux — corresponding typed objects are available too
Because each getter is an async call to the platform channel, you must await the result inside an async method or initState override.
Fetching Device Info (cross-platform safe)
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform, TargetPlatform;
Future<Map<String, String>> fetchDeviceInfo() async {
final plugin = DeviceInfoPlugin();
final info = <String, String>{};
if (defaultTargetPlatform == TargetPlatform.android) {
final android = await plugin.androidInfo;
info['Platform'] = 'Android';
info['Model'] = android.model;
info['Brand'] = android.brand;
info['SDK Version'] = android.version.sdkInt.toString();
info['OS Release'] = android.version.release;
info['Device ID'] = android.id;
info['Is Physical'] = android.isPhysicalDevice.toString();
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
final ios = await plugin.iosInfo;
info['Platform'] = 'iOS';
info['Model'] = ios.model;
info['Device Name'] = ios.name;
info['System Name'] = ios.systemName;
info['System Version']= ios.systemVersion;
info['Identifier'] = ios.identifierForVendor ?? 'unavailable';
info['Is Physical'] = ios.isPhysicalDevice.toString();
}
return info;
}
defaultTargetPlatform from package:flutter/foundation.dart instead of Platform.isAndroid from dart:io. The former works on all platforms (including Web); the latter throws on Web.Key Fields Reference
Understanding what each field means helps you choose the right one for your use case:
- Android —
model: the marketing model name (e.g.Pixel 8 Pro) - Android —
brand: the consumer-facing brand (e.g.google) - Android —
version.sdkInt: the Android SDK level (e.g.34for Android 14) - Android —
version.release: the human-readable OS version string (e.g.14) - Android —
id: a build fingerprint string; not a stable unique device ID - iOS —
identifierForVendor: a UUID unique per app-vendor per device; resets on reinstall if no other apps from the same vendor are installed - iOS —
utsname.machine: the hardware machine identifier (e.g.iPhone16,2)
identifierForVendor can change; ANDROID_ID can change after a factory reset. For analytics, prefer a server-generated UUID stored in secure storage rather than relying on hardware identifiers.Building a Settings-Style Display Screen
A common pattern is to show device metadata on a diagnostics or "About this device" settings screen. The widget loads data asynchronously in initState and stores it in a Map that drives a ListView of ListTile rows.
DeviceInfoScreen — Complete Example
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class DeviceInfoScreen extends StatefulWidget {
const DeviceInfoScreen({super.key});
@override
State<DeviceInfoScreen> createState() => _DeviceInfoScreenState();
}
class _DeviceInfoScreenState extends State<DeviceInfoScreen> {
Map<String, String> _deviceData = {};
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadDeviceInfo();
}
Future<void> _loadDeviceInfo() async {
final plugin = DeviceInfoPlugin();
final data = <String, String>{};
try {
if (defaultTargetPlatform == TargetPlatform.android) {
final android = await plugin.androidInfo;
data['Model'] = android.model;
data['Brand'] = android.brand;
data['Manufacturer'] = android.manufacturer;
data['Android SDK'] = android.version.sdkInt.toString();
data['OS Version'] = android.version.release;
data['Security Patch'] = android.version.securityPatch ?? 'N/A';
data['Is Physical'] = android.isPhysicalDevice ? 'Yes' : 'No';
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
final ios = await plugin.iosInfo;
data['Device Name'] = ios.name;
data['Model'] = ios.model;
data['Machine'] = ios.utsname.machine;
data['System Name'] = ios.systemName;
data['System Version'] = ios.systemVersion;
data['Vendor ID'] = ios.identifierForVendor ?? 'N/A';
data['Is Physical'] = ios.isPhysicalDevice ? 'Yes' : 'No';
}
} catch (e) {
data['Error'] = e.toString();
}
if (mounted) {
setState(() {
_deviceData = data;
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Device Information')),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: ListView(
children: _deviceData.entries.map((entry) {
return ListTile(
title: Text(
entry.key,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
color: Colors.grey,
),
),
subtitle: Text(
entry.value,
style: const TextStyle(fontSize: 16),
),
dense: true,
);
}).toList(),
),
);
}
}
Best Practices
- Call once, cache the result — platform channel calls have overhead; store the returned data in a variable or a state management provider rather than calling the plugin repeatedly.
- Guard with
mounted— always checkif (mounted)before callingsetStateinside async methods to avoid setting state on a disposed widget. - Wrap in try/catch — on unsupported platforms or during testing, the plugin may throw; graceful error handling keeps your app stable.
- Do not hard-code SDK levels — compare
android.version.sdkIntagainst named constants (e.g.Build.VERSION_CODES.TIRAMISU= 33) or well-documented integer values rather than magic numbers.
device_info_plus provides a simple async API to read typed, platform-specific device metadata. Create one DeviceInfoPlugin instance, await the correct platform getter, and store the result. Guard async state updates with mounted, and prefer a server-generated UUID over hardware identifiers for tracking purposes.