MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
SharedPreferences and DataStore are where small but sensitive values quietly accumulate. Here's how I keep that local storage secure — what to encrypt, what to keep in the Keystore, and what not to store at all.
On this page
Local key-value storage feels harmless. It's where you stash a theme preference, an onboarding flag, a token. The problem is that the token doesn't look more dangerous than the flag when you're writing the code, and plain preferences files are readable on a rooted or compromised device. Treating all local storage as plaintext-by-default is the mindset that keeps this safe.
Not every preference needs protection. A boolean for "dark mode on" is not a secret. An auth token, a refresh token, a user's personal detail, an encryption key — those are. The first step is honest classification: walk your stored keys and separate the genuinely sensitive ones from the cosmetic ones. Over-encrypting everything adds complexity for no benefit; under-encrypting the few that matter is the actual risk. Spend the effort where the data is sensitive.
DataStore is the modern replacement for SharedPreferences, and I use it for new code. It's asynchronous and Flow-based, so it doesn't block the main thread the way
SharedPreferences.commit()val THEME = booleanPreferencesKey("dark_theme")
val isDark: Flow<Boolean> = context.dataStore.data
.map { it[THEME] ?: false }DataStore alone doesn't encrypt at rest, though — it solves the threading and API problems, not the secrecy one. So for sensitive values, encryption is still your job.
For secrets, the value should be encrypted with a key held in the Android Keystore, where the key material lives in hardware-backed storage the app process can't read out directly. The pattern is: generate or retrieve a Keystore key, use it to encrypt the value, store the ciphertext. Even if someone pulls the storage file off the device, the ciphertext is useless without the hardware-bound key. This is the meaningful protection — encryption tied to the device's secure hardware, not a key sitting in your code.
The strongest protection is not storing the secret at all. Before persisting a token, I ask whether it needs to live on disk or can be held in memory for the session and re-fetched when needed. Short-lived tokens that can be refreshed don't always need persistent storage; keeping them in memory means there's nothing on disk to steal. Every secret you don't persist is a secret you don't have to defend, and the simplest secure storage is the absence of stored secrets.
Security isn't only about writing — it's about clearing. On logout, sensitive values must actually be wiped, not just overwritten with a flag that says "logged out" while the token still sits in storage. The same applies to account deletion and the user's right to erasure: when they're gone, their data should be genuinely gone from local storage. I test this path explicitly, because a logout that leaves credentials behind is a quiet vulnerability that no normal usage reveals.
The reason local storage gets neglected is that it's invisible during normal use — everything works whether or not it's encrypted. That invisibility is exactly why it needs a deliberate habit: every time I add a stored value, I classify it, and sensitive ones get the encrypted path automatically. Folding this into the moment of writing the code, rather than auditing for it later, is what keeps a portfolio of apps consistently safe without a security review on every release.
The reason this deserves a deliberate habit rather than a one-off audit is that storage accretes slowly. No single commit adds a dangerous amount of stored data; it's the accumulation over months of features, each stashing one more value, that quietly builds a pile of unprotected sensitive data nobody decided to create. By the time you'd think to audit it, the risky values are scattered across the codebase and easy to miss. Classifying each value at the moment you write it keeps the protection proportional and current, and it costs almost nothing in the flow of normal work. Security that's built into the writing is the only kind that reliably keeps pace with a growing app.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus