Android offers several ways to persist data locally. Choose the right option based on data size, structure, privacy, and whether the user should access files outside your app.
SharedPreferences stores primitive data (String, Int, Boolean, Float, Long) as key-value pairs. Data persists across app restarts and is private to your app.
Create — get SharedPreferences instance
Kotlin — Open preferences file// Default preferences file (packageName_preferences.xml)
val prefs = getSharedPreferences("app_settings", Context.MODE_PRIVATE)
// Or use the default preferences
val defaultPrefs = PreferenceManager.getDefaultSharedPreferences(this)
Create / Update — write values
Kotlin — Save settings (Create + Update)fun saveUserSettings(username: String, darkMode: Boolean, fontSize: Int) {
prefs.edit().apply {
putString("username", username)
putBoolean("dark_mode", darkMode)
putInt("font_size", fontSize)
putFloat("volume", 0.8f)
putLong("last_login", System.currentTimeMillis())
apply() // Async — preferred on main thread
// commit() returns Boolean — use when you need synchronous result
}
}
Read — retrieve values
Kotlin — Read preferences with defaultsval username = prefs.getString("username", "Guest") ?: "Guest"
val darkMode = prefs.getBoolean("dark_mode", false)
val fontSize = prefs.getInt("font_size", 16)
val volume = prefs.getFloat("volume", 1.0f)
val lastLogin = prefs.getLong("last_login", 0L)
// Check if key exists
val hasUsername = prefs.contains("username")
Kotlin — OnSharedPreferenceChangeListenerprivate val prefListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPrefs, key ->
when (key) {
"dark_mode" -> {
val enabled = sharedPrefs.getBoolean(key, false)
applyTheme(if (enabled) Theme.DARK else Theme.LIGHT)
}
}
}
override fun onResume() {
super.onResume()
prefs.registerOnSharedPreferenceChangeListener(prefListener)
}
override fun onPause() {
prefs.unregisterOnSharedPreferenceChangeListener(prefListener)
super.onPause()
}
Tip: Use apply() for async writes on the UI thread. Use commit() only when you need a synchronous boolean result. Never store passwords or tokens in plain SharedPreferences — use EncryptedSharedPreferences instead.
2Internal Storage
Internal storage is private to your app. Files are deleted when the app is uninstalled. Other apps cannot access them without root.
Internal storage locations
Kotlin — Internal directories// Files directory — persistent app files
val filesDir = filesDir // /data/data/com.example.app/files/
val file = File(filesDir, "notes.txt")
// Cache directory — can be cleared by system when low on space
val cacheDir = cacheDir // /data/data/com.example.app/cache/
// Custom subdirectory
val notesDir = File(filesDir, "notes")
if (!notesDir.exists()) notesDir.mkdirs()
Kotlin — Manage internal files// List all files in filesDir
val fileList = filesDir.listFiles()?.map { it.name } ?: emptyList()
// Delete a file
deleteFile("notes.txt")
// Or using File API
File(filesDir, "notes.txt").delete()
3External Storage
External storage (shared storage) holds larger files like photos and downloads. Modern Android uses scoped storage — apps access app-specific directories without broad permissions, and need permissions or MediaStore for shared files.
Manifest permissions (when needed)
XML — AndroidManifest.xml (legacy / specific cases)<!-- Android 12 and below for broad file access -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<!-- Android 13+ for media types -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
App-specific external directory (no permission needed)
Kotlin — getExternalFilesDir()// App-specific external folder — removed on uninstall
val externalDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
// /storage/emulated/0/Android/data/com.example.app/files/Pictures/
val photoFile = File(externalDir, "profile_${System.currentTimeMillis()}.jpg")
// Cache on external storage
val externalCache = externalCacheDir
Check storage state
Kotlin — External storage availabilityfun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
fun isExternalStorageReadable(): Boolean {
val state = Environment.getExternalStorageState()
return state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY
}
Scoped storage summary
Location
Permission Required
Removed on Uninstall
getExternalFilesDir()
No
Yes
getExternalCacheDir()
No
Yes
MediaStore (Photos, Downloads)
READ_MEDIA_* or picker
No (user files)
Storage Access Framework (SAF)
User picks file via picker
Depends on file
4Reading/Writing Files
Beyond SharedPreferences, apps read and write text, JSON, and binary files using Kotlin I/O APIs. Always use coroutines or background threads for large file operations.
Kotlin — Open document pickerval pickFileLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri: Uri? ->
uri?.let {
contentResolver.openInputStream(it)?.use { stream ->
val text = stream.bufferedReader().readText()
binding.tvImported.text = text
}
}
}
// Launch picker
binding.btnImport.setOnClickListener {
pickFileLauncher.launch(arrayOf("text/plain", "application/json"))
}
5Data Persistence
Data persistence means your app retains data across process death, configuration changes, and app restarts. Choose the right layer for each type of data.
Persistence strategy guide
Data Type
Solution
Example
User preferences / toggles
SharedPreferences
Dark mode, language, onboarding done
Small JSON / text documents
Internal file
Draft notes, cached API response
Images / large media
External app-specific dir
Captured photos, exported PDFs
Structured lists / relations
Room database
Contacts, todos, chat messages
Sensitive secrets
EncryptedSharedPreferences / Keystore
Auth tokens, API keys
UI state during rotation
ViewModel + SavedStateHandle
Form input, scroll position
Repository pattern for persistence
Kotlin — SettingsRepository.ktclass SettingsRepository(context: Context) {
private val prefs = context.getSharedPreferences("app_settings", Context.MODE_PRIVATE)
private val gson = Gson()
private val profileFile = File(context.filesDir, "profile.json")
var darkMode: Boolean
get() = prefs.getBoolean("dark_mode", false)
set(value) = prefs.edit().putBoolean("dark_mode", value).apply()
var username: String
get() = prefs.getString("username", "") ?: ""
set(value) = prefs.edit().putString("username", value).apply()
fun saveProfile(profile: UserProfile) {
profileFile.writeText(gson.toJson(profile))
}
fun loadProfile(): UserProfile? {
if (!profileFile.exists()) return null
return gson.fromJson(profileFile.readText(), UserProfile::class.java)
}
fun clearAllData() {
prefs.edit().clear().apply()
profileFile.delete()
}
}