react-native-app-device-info
v1.0.0
Published
Minimal, fast, synchronous app & device info for React Native — app version, build number, OS version, device name/type and a persistent unique device id. iOS + Android, Old & New Architecture.
Maintainers
Readme
react-native-app-device-info
Minimal, fast, synchronous app and device information for React Native.
Every value is read from native constants that are computed once when the app
starts. From JavaScript there is no Promise, no await, and no bridge
round-trip. Every getter is a plain synchronous function call. The library
works on iOS and Android, on both the Old and the New Architecture.
import AppDeviceInfo from 'react-native-app-device-info';
AppDeviceInfo.getVersion(); // "1.2.3"
AppDeviceInfo.getBuildNumber(); // "42"
AppDeviceInfo.getUniqueId(); // a stable id for this deviceTable of contents
- Why this library
- Requirements
- Installation
- Quick start
- API reference
- Field notes
- How it works
- TypeScript
- Platform support matrix
- Troubleshooting
- License
Why this library
- Small. One native module, about twenty getters, zero runtime dependencies.
- Fast. Values are exposed as native constants, so reading them is just a property access. There is no message sent over the bridge and no startup penalty.
- Simple. Synchronous getters. No callbacks, no promises, nothing to wait for.
- Consistent. The same API and the same return types on iOS and Android.
- Future proof. Identical behaviour on the legacy bridge and the New Architecture (bridgeless / TurboModules interop).
Requirements
- React Native 0.64 or newer (Old or New Architecture).
- iOS 12.0 or newer.
- Android API level 21 (Android 5.0) or newer.
Installation
npm install react-native-app-device-infoor
yarn add react-native-app-device-infoThis package contains native code, so you must rebuild the app after installing it. A Fast Refresh / Metro reload is not enough.
iOS
Install the CocoaPods dependency, then rebuild:
cd ios && pod install && cd ..
npx react-native run-iosAndroid
Autolinking handles everything. Just rebuild the app:
npx react-native run-androidExpo
This is a native module, so it does not run in Expo Go. Use a development build:
npx expo install react-native-app-device-info
npx expo prebuild
npx expo run:ios # or: npx expo run:androidQuick start
You can import the default object and call methods on it:
import AppDeviceInfo from 'react-native-app-device-info';
console.log(AppDeviceInfo.getReadableVersion()); // "1.2.3 (42)"
console.log(AppDeviceInfo.getOsName(), AppDeviceInfo.getOsVersion()); // "iOS" "17.4"
console.log(AppDeviceInfo.getUniqueId());Or import only the functions you need:
import { getVersion, getBuildNumber, getInfo } from 'react-native-app-device-info';
const version = getVersion();
const build = getBuildNumber();
const everything = getInfo();Both styles are equivalent and fully typed.
API reference
Every function is synchronous and returns immediately. The tables show the return type and the native source on each platform.
App information
| Function | Returns | iOS | Android |
| ---------------------- | -------- | ---------------------------- | ---------------------------------------------------- |
| getVersion() | string | CFBundleShortVersionString | versionName |
| getBuildNumber() | string | CFBundleVersion | versionCode |
| getBundleId() | string | bundleIdentifier | packageName (runtime applicationId, incl. suffix) |
| getBaseBundleId() | string | bundle id without suffix | applicationId without suffix |
| getAppName() | string | display name | application label |
| getReadableVersion() | string | "1.2.3 (42)" | "1.2.3 (42)" |
getVersion() is the human-facing version (the "version name" / "build name").
getBuildNumber() is the internal incrementing number (the "build number" /
"version code").
Device and OS information
| Function | Returns | iOS | Android |
| ------------------- | -------------------- | --------------------- | ------------------------ |
| getOsName() | string | "iOS" | "Android" |
| getOsVersion() | string | systemVersion | Build.VERSION.RELEASE |
| getDeviceType() | 'ios' \| 'android' | "ios" | "android" |
| getDeviceModel() | string | e.g. "iPhone15,2" | Build.MODEL |
| getDeviceBrand() | string | "Apple" | Build.MANUFACTURER |
| isTablet() | boolean | iPad? | large screen? |
| getUniqueId() | string | identifierForVendor | Settings.Secure.ANDROID_ID |
| isEmulator() | boolean | running on simulator? | running on emulator? |
| getDeviceLocale() | string | e.g. "en-US" | e.g. "en-US" |
| getTimezone() | string | e.g. "America/New_York" | e.g. "America/New_York" |
| getApiLevel() | number | 0 (not applicable) | Build.VERSION.SDK_INT |
Install information
| Function | Returns | iOS | Android |
| ----------------------- | -------- | ------------------------------------------ | ----------------------------------------------------------------- |
| getInstallerSource() | string | "AppStore" / "TestFlight" / "Other" | installing package, e.g. "com.android.vending", or "" |
| getFirstInstallTime() | number | epoch milliseconds (heuristic) | epoch milliseconds (exact) |
| getLastUpdateTime() | number | epoch milliseconds (heuristic) | epoch milliseconds (exact) |
Times are epoch milliseconds. Wrap a value with new Date(ms) to get a
Date. A value of 0 means the time could not be determined.
Get everything at once
getInfo() returns every value in a single object. The result is computed once
and cached, so calling it repeatedly is free.
import { getInfo } from 'react-native-app-device-info';
const info = getInfo();The returned object has this exact shape:
interface AppDeviceInfoConstants {
// App
appVersion: string; // "1.2.3"
buildNumber: string; // "42"
bundleId: string; // "com.acme.app"
appName: string; // "Acme"
// Device and OS
osName: string; // "iOS" | "Android"
osVersion: string; // "17.4"
deviceType: 'ios' | 'android';
deviceModel: string; // "iPhone15,2" | "Pixel 8"
deviceBrand: string; // "Apple" | "Google"
isTablet: boolean;
deviceId: string; // stable per-device id
isEmulator: boolean;
deviceLocale: string; // "en-US"
timezone: string; // "America/New_York"
apiLevel: number; // 34 on Android, 0 on iOS
// Install
installerSource: string; // "AppStore" | "com.android.vending" | ...
firstInstallTime: number; // epoch ms, 0 if unknown
lastUpdateTime: number; // epoch ms, 0 if unknown
}Note that getInfo().appVersion is the value behind getVersion(),
getInfo().buildNumber is the value behind getBuildNumber(), and so on. The
individual getters are thin convenience wrappers around this object.
Field notes
This section explains the values that need context so you choose the right one and know exactly what it guarantees.
getUniqueId
A stable id for the device that persists across app launches and app updates.
- iOS returns
identifierForVendor. It stays the same while at least one app from the same vendor (the same Apple Developer account) is installed. If the user deletes every app from that vendor and reinstalls, a new id is generated. - Android returns
Settings.Secure.ANDROID_ID. It stays the same until a factory reset. On Android 8.0 and newer the value is scoped to your app's signing key, so different apps see different ids on the same device.
This is the identifier that both platforms recommend for ordinary app use. It needs no extra permissions and does not expose a hardware serial number. It is not designed for cross-vendor tracking and is not guaranteed to be globally unique across the entire world, only stable for your app on a given device.
getBundleId vs getBaseBundleId
getBundleId()returns the identifier of the build that is actually running. On Android this includes anyapplicationIdSuffixfrom your Gradle config, so a debug build can reportcom.acme.app.debug. On iOS it is whatever bundle identifier the running build was signed with, including any.devor.stagingsuffix.getBaseBundleId()returns the same value with one recognised build-variant suffix removed. For examplecom.acme.app.debugbecomescom.acme.app.
The suffixes that are stripped are: debug, dev, development, staging,
stg, qa, test, alpha, beta, release, internal. Only the final
segment is checked, and only if it matches that list, so a normal id such as
com.acme.app is never shortened by mistake. Use getBaseBundleId() when you
want one identity for an app across all of its build variants, for example as a
key in analytics or backend records.
getInstallerSource
Tells you where the installed build came from.
- Android returns the package name of the installer. Common values are
com.android.vending(Google Play) andcom.amazon.venezia(Amazon Appstore). A build installed withadb, a file manager, or a CI device farm usually returns an empty string. - iOS has no public installer API, so the library infers the source from
the App Store receipt:
"TestFlight"for a TestFlight build,"AppStore"for a build delivered through the App Store, and"Other"for development, simulator, or sideloaded builds.
Install and update times
getFirstInstallTime() and getLastUpdateTime() return epoch milliseconds.
- Android reads exact values from
PackageInfo.firstInstallTimeandPackageInfo.lastUpdateTime. - iOS has no public API for these, so the library uses a filesystem heuristic: the creation date of the app's Documents directory approximates the first install, and the modification date of the app executable approximates the last update. Treat the iOS numbers as a best effort rather than an exact record.
isEmulator
Returns true when the app is running on a simulator (iOS) or an emulator
(Android).
- iOS uses a compile-time simulator flag, so the result is reliable.
- Android has no single official flag, so the library inspects standard
Buildproperties (fingerprint, model, hardware, manufacturer, product). This detects the common emulators, including the Android Studio AVDs and Genymotion. Treat it as a strong best effort rather than a guarantee.
How it works
The native module implements the classic React Native constants API
(constantsToExport on iOS, getConstants() on Android). The platform builds
a single dictionary of values when the module is created and hands it to
JavaScript. The JavaScript layer reads that dictionary once, normalises every
value, caches the result, and returns it from each getter.
Because the values are constants rather than method calls, there is no asynchronous message and no serialization cost at call time. This is why every function in the API can be synchronous, and why the library adds effectively nothing to startup beyond reading a handful of system properties.
The JavaScript layer also supports both shapes that React Native can hand it:
constants attached directly to the module object (legacy bridge) and a
getConstants() method (New Architecture interop). You do not need to configure
anything for either case.
TypeScript
The package ships its own type definitions. The default export, every named
function, and the AppDeviceInfoConstants interface are fully typed. No
@types package is required.
import AppDeviceInfo, {
getInfo,
type AppDeviceInfoConstants,
} from 'react-native-app-device-info';
const info: AppDeviceInfoConstants = getInfo();Platform support matrix
| Value | iOS | Android |
| ------------------ | ------------------------- | ---------------------- |
| App version | Yes | Yes |
| Build number | Yes | Yes |
| Bundle / package id| Yes | Yes |
| Base bundle id | Yes | Yes |
| App name | Yes | Yes |
| OS name / version | Yes | Yes |
| Device type | Yes | Yes |
| Device model | Yes | Yes |
| Device brand | "Apple" | Yes |
| Is tablet | Yes | Yes |
| Unique id | Yes (identifierForVendor) | Yes (ANDROID_ID) |
| Is emulator | Yes (reliable) | Yes (heuristic) |
| Locale | Yes | Yes |
| Timezone | Yes | Yes |
| API level | 0 (not applicable) | Yes |
| Installer source | Yes (inferred) | Yes |
| First install time | Heuristic | Yes (exact) |
| Last update time | Heuristic | Yes (exact) |
Troubleshooting
The module is not linked / values throw an error.
Rebuild the native app after installing. A Metro reload does not include new
native code. On iOS, run pod install first.
It does not work in Expo Go.
Expo Go cannot load custom native modules. Use a development build with
npx expo prebuild and npx expo run:ios or npx expo run:android.
getBuildNumber() returns a different value than I set.
It returns the value of the build that is actually running. Make sure you are
looking at the same build configuration (debug vs release) that you edited.
getApiLevel() returns 0.
That is expected on iOS, which has no Android-style API level. Use
getOsVersion() on iOS instead.
getFirstInstallTime() looks wrong on iOS.
iOS does not expose an install timestamp, so the value is a filesystem-based
estimate. Use the Android values when you need exact timing.
License
MIT
