Kotlin Copilot: Jetpack compose get device ID
Video: Kotlin Copilot: Jetpack compose get device ID by Taught by Celeste AI - AI Coding Coach
Jetpack Compose: Get the Device ID
Settings.Secure.ANDROID_IDviaLocalContext. Wrap inrememberso it's read once. Useful for analytics; risky for anything that requires a stable identifier across factory resets.
A small puzzle: display the Android ANDROID_ID in a Composable. Two pieces — accessing the system context from a Composable, and remembering the read so it doesn't repeat on recomposition.
The Copilot prompt
import androidx.compose.runtime.Composable
// Composable that displays the Android device ID
@Composable
fun DeviceIdDisplay() {
Copilot completes:
import android.content.Context
import android.provider.Settings
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@Composable
fun DeviceIdDisplay() {
val context = LocalContext.current
val deviceId = remember {
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
}
Text("Device ID: $deviceId")
}
Walkthrough
Three pieces.
1. LocalContext.current. LocalContext is a CompositionLocal provided automatically by Compose's Activity/Application setup. .current reads the current value — the Context of the hosting Activity.
Composables don't have a Context parameter by default. LocalContext is the bridge to Android-platform APIs (resources, services, content resolvers).
2. remember { ... } to cache the read. Without remember, the Settings.Secure.getString call would run on every recomposition — wasteful for a value that never changes.
remember caches the lambda's return value. With no key, it caches once for the Composable's lifetime in this composition.
3. Settings.Secure.ANDROID_ID. A 64-bit hex string. Generated at first boot; stays constant until factory reset.
Important caveats
ANDROID_ID isn't a reliable unique identifier:
- Resets on factory reset. Two devices that have been wiped both produce new IDs.
- Per-app on Android 8+. Each signing key sees a different ID for the same device. Apps with the same signing key see the same ID.
- Same on all users. If the device has multiple users, each user's apps see a separate ID.
- Was unreliable on older Android. Pre-O, some devices returned
nullor a hardcoded"9774d56d682e549c".
For analytics or per-install identification: use Firebase Installations or an app-generated UUID stored in app preferences.
For DRM/anti-fraud: there's no truly stable cross-app identifier on Android, by design. Apple's IDFA had the same trajectory.
A more realistic device-info display
@Composable
fun DeviceInfo() {
val context = LocalContext.current
val info = remember {
DeviceInfo(
androidId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID),
manufacturer = Build.MANUFACTURER,
model = Build.MODEL,
sdkVersion = Build.VERSION.SDK_INT,
versionCode = Build.VERSION.RELEASE,
)
}
Column {
Text("Android ID: ${info.androidId}")
Text("${info.manufacturer} ${info.model}")
Text("Android ${info.versionCode} (SDK ${info.sdkVersion})")
}
}
data class DeviceInfo(
val androidId: String,
val manufacturer: String,
val model: String,
val sdkVersion: Int,
val versionCode: String,
)
Build.MANUFACTURER, Build.MODEL, Build.VERSION.* come from android.os.Build — they don't need a Context.
remember vs derivedStateOf
// Once on first composition, cached forever:
val deviceId = remember { Settings.Secure.getString(...) }
// Re-runs whenever the dependency changes:
val deviceId by remember(context) {
derivedStateOf { Settings.Secure.getString(context.contentResolver, ANDROID_ID) }
}
For a value that never changes within a composition's lifetime (like ANDROID_ID), plain remember is correct. derivedStateOf is for values that change as their inputs change.
For "compute once," remember wins on simplicity.
Alternatives: hoist out of the Composable
@Composable
fun DeviceIdDisplay(deviceId: String) {
Text("Device ID: $deviceId")
}
// Caller:
val context = LocalContext.current
val deviceId = remember { Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) }
DeviceIdDisplay(deviceId)
Hoisting state up makes the inner Composable testable without a real Context. Standard Compose pattern: read system state at the top, pass plain values down.
For a UI test, you'd just call DeviceIdDisplay(deviceId = "test-id") with no Context wiring.
A word on permissions
ANDROID_ID requires no special permission. Reading it from a Composable on launch is fine.
For other device-identifying APIs:
IMEI/IMSI/ phone numbers — requireREAD_PHONE_STATE(and often a Play Store justification).MAC address— restricted on Android 6+; returns a constant fake value.Serial number(Build.SERIAL) — returns"unknown"on Android 8+.
The trend is clear: Android is making cross-app device fingerprinting increasingly difficult. Lean on app-scoped IDs from Firebase or your own preferences.
Common mistakes
Reading without remember. Recomposition triggers Settings.Secure.getString repeatedly. For a constant value, that's wasted work.
Treating ANDROID_ID as unique. It's per-app-signing-key-per-user on Android 8+. Multiple installs of your app on one device share an ID; other apps see a different ID.
Calling from non-Activity Context. Settings.Secure.getString works on the Application Context too, but if you accidentally pass a null content resolver (rare), it crashes.
Using Build.SERIAL. Deprecated since Android 8 — returns "unknown".
Storing the ID server-side without privacy disclosure. It's a stable-ish identifier; treat it as PII for compliance.
What's next
Episode 8: Set up a Ktor client and fetch JSON. Build a typed API client using Ktor, kotlinx.serialization, and runBlocking for a quick demo.
Recap
LocalContext.current to access the platform context from a Composable. remember { ... } to compute once. Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) for the device ID. Caveats: not unique across factory reset, per-signing-key on Android 8+, never trust for security. For UI tests, hoist the ID out of the Composable as a parameter. Other identifying APIs (IMEI, MAC, serial) are mostly restricted on modern Android.
Next episode: Ktor client setup.