device_profile 0.1.0
device_profile: ^0.1.0 copied to clipboard
What the device is: the name a person knows it by (Redmi 9A, Galaxy XCover 5, iPhone 12 mini) instead of its hardware code, the kind of device, and whether its display is round.
device_profile #
A Flutter plugin that tells what the device is, from one read of the platform: the name a person knows it by - "Redmi 9A", "Galaxy XCover 5", "iPhone 12 mini" - instead of the hardware code the platform reports as the model ("M2006C3LG", "SM-G525F", "iPhone"), the kind of device, and whether its display is round.
A list of signed-in devices - a sessions screen, a "too many devices" prompt - usually shows Build.MODEL or
UIDevice.model. On many Android phones that is a code nobody recognizes, and on every iPhone it is just
"iPhone", so a user with two of them cannot tell which one to sign out. No single field holds the real name on
every phone: each maker keeps it somewhere else. This plugin knows where, reads it, and picks the best one.
Features #
- The maker's own name - read where each maker keeps it: Samsung, Xiaomi/Redmi/POCO, OPPO, realme, OnePlus, Huawei, Honor, vivo, Lenovo, Motorola, Tecno/Infinix/itel, ZTE/nubia, HMD, Sony, Asus, LG and more.
- Never a wrong device - codes, placeholders and codenames are rejected; when nothing better is there, you get
the brand and the code ("Samsung SM-G525F") and
isMarketingNametells you so. - The device kind - phone, tablet, foldable, watch, TV, car, desktop or headset, for the right icon.
- The shape of the display - round or rectangular, as Android's
Configuration.isScreenRoundreports it. A round Wear OS face and a square one are both a square window to Flutter; a watch layout that keeps clear of a round bezel needs to know which one it is on. - The user's own name, only if you want it - "Anna's XCover 5" tells two Galaxies apart, but it can carry a person's name, so it is off unless you ask.
- Ready for a sessions list -
fullNamewith the brand in front, auserAgentTokensafe for a header, anddistinctLabelsfor two devices that share a name. - Offline and small - no network, no device database: only what the device itself carries, plus a table of Apple identifiers.
- No permissions, never throws.
Requirements #
- Flutter 3.47 or later.
- Android 7.0 (API 24) or later, built with Android Gradle Plugin 8.13 or later.
- iOS 15 or later.
Installation #
dependencies:
device_profile: ^0.1.0
flutter pub get
Usage #
import 'package:device_profile/device_profile.dart';
final device = await const PlatformDeviceProfile().read();
device?.name.name; // "Galaxy Tab A7"
device?.name.fullName; // "Samsung Galaxy Tab A7"
device?.name.isMarketingName; // true - false when only the brand and the code were left
device?.name.source; // NameSource.factorySetting - where the name came from
device?.kind; // DeviceKind.tablet
device?.screenShape; // ScreenShape.rectangular - ScreenShape.round on a round Wear OS face
read() returns null only on a platform it does not support (web, desktop) or when the platform could not be
asked. The device is asked once; later calls answer from memory.
The name the user gave the device #
const PlatformDeviceProfile(userAssignedName: UserAssignedName.preferred);
| Mode | What you get on a renamed Galaxy XCover 5 |
|---|---|
never (default) |
"Galaxy XCover 5" |
fallback |
the maker's name, and the user's name only where the maker's is missing |
preferred |
"Anna's XCover 5" - the user's name first; a phone never renamed still gets "Galaxy XCover 5" |
On iOS an app cannot read the name the user gave the device, so every mode gives the model name.
A list of devices #
// For a User-Agent header or any one-line field: no ; ( ), ASCII only.
device.name.userAgentToken; // "Lenovo Tab M8 [4th Gen]", "Ганна's M32" -> "%D0%93...'s M32"
// Two devices with one name:
distinctLabels([
(name: 'Galaxy S24', detail: 'Android 15'),
(name: 'Galaxy S24', detail: 'Android 14'),
]); // ["Galaxy S24 (Android 15)", "Galaxy S24 (Android 14)"]
Uri.decodeComponent turns a userAgentToken back into the name.
The same escaping is there for any other value that goes into the same header, such as the model you send when
read() gave no name:
userAgentToken(device?.name.fullName ?? model); // model as device_info_plus reports it: "SM-G525F"
Testing #
Depend on the DeviceProfileReader interface and pass a fake:
class FakeDeviceProfile implements DeviceProfileReader {
@override
Future<DeviceProfile?> read() async => const DeviceProfile(
name: DisplayName('Pixel 9', NameSource.model),
kind: DeviceKind.phone,
screenShape: ScreenShape.rectangular,
);
}
Where the name comes from #
| Platform | Source |
|---|---|
| Android | Only the keys of the device's own maker are read, in this order: the maker's factory name in settings (Samsung default_device_name, Huawei unified_default_device_name, vivo's hotspot name), the maker's marketing properties (ro.product.marketname on Xiaomi, ro.vendor.oplus.market.name on OPPO/realme/OnePlus, ro.config.marketing_name on Huawei and so on), Build.MODEL when it already is a name (Pixel, Motorola), the brand and the code. |
| iOS | The hardware identifier (iPhone13,1) looked up in a table of Apple identifiers; the identifier itself for a device newer than the table. |
| Others | null. |
The kind comes from the Android system features, the build's own ro.build.characteristics and the screen width,
or from the iOS interface idiom. The shape comes from Android's Configuration.isScreenRound; on iOS it is always
rectangular, since no iPhone or iPad display is round.
Things to know #
- A few vivo models keep their name from apps. vivo Y100, Y22 and Y21 carry no name an app may read; they get the brand and the code ("vivo V2239").
- Honor and Asus keys come from firmware research and have not been run on a real phone yet.
- New iPhones need the table updated:
dart run tool/apple_identifiers/generate.dart, withtool/apple_identifiers/overrides.jsonfor models the upstream list does not have yet. - The user's name is personal data. Keep
userAssignedNameatneverunless the name stays with the user, such as a list of their own devices. - The name is as the maker writes it - "motorola edge 50 fusion" in lower case, "Lenovo Tab M8 (4th Gen)" with
parentheses. Use
userAgentTokenwhere those characters matter.
Verified devices - 42 Android devices and two iOS simulators
The example app at targetSdk 36. The kind is phone unless the name says otherwise. "Run":
device - a device at hand; BrowserStack - a BrowserStack App Live device; BrowserStack values - the
values a BrowserStack device reported, the name checked by a unit test; simulator - an iOS simulator.
preferred is the name in that mode where it differs; (owner) stands for the name of the person who renamed
the phone.
| Maker | Device | Android | Name | Source | preferred |
Run |
|---|---|---|---|---|---|---|
| Xiaomi | Redmi 9A | 10 | Redmi 9A | ro.product.marketname |
Redmi 9A (persist.sys.device_name) |
device |
| Xiaomi | Redmi A5 | 15 | Redmi A5 | ro.product.marketname |
device | |
| Xiaomi | Redmi Note 11 | 11 | Redmi Note 11 | ro.product.marketname |
BrowserStack | |
| Xiaomi | Redmi Note 12 4G | 13 | Redmi Note 12 | ro.product.marketname |
BrowserStack | |
| Xiaomi | Redmi Note 12 Pro | 12 | Redmi Note 12 Pro 5G | ro.product.marketname |
BrowserStack | |
| Xiaomi | Redmi Note 13 Pro 5G | 14 | Redmi Note 13 Pro 5G | ro.product.marketname |
BrowserStack | |
| Xiaomi | Redmi Note 14 Pro 5G | 15 | Redmi Note 14 Pro 5G | ro.product.marketname |
BrowserStack | |
| Samsung | Galaxy XCover 5 | 14 | Galaxy XCover 5 | default_device_name |
(owner)'s XCover 5 | device |
| Samsung | Galaxy M32 | 13 | Galaxy M32 | default_device_name |
(owner)'s M32 | device |
| Samsung | Galaxy Tab A7 | 12 | Galaxy Tab A7 - tablet | default_device_name |
device | |
| Samsung | Galaxy S26 | 16 | Galaxy S26 | default_device_name |
BrowserStack | |
| Samsung | Galaxy S22+ | 12 | Galaxy S22+ | default_device_name |
BrowserStack | |
| Huawei | nova 11i | 12 | HUAWEI nova 11i | unified_default_device_name |
device | |
| Huawei | nova 11 SE | 12 | HUAWEI nova 11 SE | unified_default_device_name, brand added |
BrowserStack values | |
| Huawei | nova 13 | 12 | HUAWEI nova 13 | unified_default_device_name, brand added |
BrowserStack values | |
| Lenovo | Tab M8 (4th Gen) | 13 | Lenovo Tab M8 (4th Gen) - tablet | ro.odm.lenovo.series |
device | |
| Pixel 9 | 17 | Pixel 9 | Build.MODEL |
device | ||
| OPPO | Reno8 T 5G | 14 | OPPO Reno8 T 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| OPPO | A78 5G | 13 | OPPO A78 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| OPPO | A54 | 11 | OPPO A54 | ro.vendor.oplus.market.name |
BrowserStack | |
| OPPO | A96 | 12 | OPPO A96 | ro.vendor.oplus.market.name |
BrowserStack | |
| OPPO | F29 5G | 15 | OPPO F29 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| OPPO | K14x 5G | 16 | OPPO K14x 5G | ro.vendor.oplus.market.enname |
BrowserStack | |
| realme | 11x 5G | 13 | realme 11x 5G | ro.vendor.oplus.market.enname |
BrowserStack | |
| realme | 12+ 5G | 14 | realme 12+ 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| realme | P3 5G | 15 | realme P3 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| realme | P4 Power 5G | 16 | realme P4 Power 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| OnePlus | 11R 5G | 13 | OnePlus 11R 5G | ro.vendor.oplus.market.name |
BrowserStack | |
| OnePlus | 12R | 14 | OnePlus 12R | ro.vendor.oplus.market.name |
BrowserStack | |
| OnePlus | 13R | 15 | OnePlus 13R | ro.vendor.oplus.market.name |
BrowserStack | |
| OnePlus | 15R | 16 | OnePlus 15R | ro.vendor.oplus.market.name |
BrowserStack | |
| Motorola | edge 40 neo | 13 | motorola edge 40 neo | Build.MODEL |
BrowserStack | |
| Motorola | edge 50 fusion | 14 | motorola edge 50 fusion | Build.MODEL |
BrowserStack | |
| Motorola | edge 60 fusion | 15 | motorola edge 60 fusion | Build.MODEL |
BrowserStack | |
| Motorola | edge 70 fusion | 16 | motorola edge 70 fusion | Build.MODEL |
BrowserStack | |
| Motorola | moto g71 5G | 11 | moto g71 5G | Build.MODEL |
BrowserStack | |
| vivo | V30 | 14 | vivo V30 | wifi_hotspot_default_name |
BrowserStack values | |
| vivo | T4x 5G | 15 | vivo T4x 5G | wifi_hotspot_default_name |
BrowserStack values | |
| vivo | T5x 5G | 16 | vivo T5x 5G | ro.vivo.product.release.name, brand added |
BrowserStack values | |
| vivo | Y100 | 13 | vivo V2239 | brand and code - nothing readable names it | BrowserStack | |
| vivo | Y22 | 12 | vivo V2207 | brand and code (the name is only in bluetooth_name, closed to apps on API 32+) |
BrowserStack | |
| vivo | Y21 | 11 | vivo V2111 | brand and code (as Y22) | BrowserStack | |
| Apple | iPhone 17e | iOS simulator | iPhone 17e - phone | identifier table | simulator | |
| Apple | iPad Pro 13-inch (M5) | iOS simulator | iPad Pro 13-inch (M5) - tablet | identifier table | simulator |
The device kind was checked on Galaxy Tab A7 and Lenovo Tab M8 (tablet), Redmi A5, HUAWEI nova 11i and Galaxy M32
(phone) and both simulators; a round Wear OS emulator (192 dp) gave watch and ScreenShape.round. A foldable
and a TV have not been run.
Repository #
| Path | What |
|---|---|
lib/ |
the plugin |
example/ |
an example app that shows the name, the kind, the shape and every source the plugin read |
tool/apple_identifiers/ |
generates the table of Apple identifiers |
License #
MIT - see LICENSE.