Almost all applications that we use or develop on Android need to store data persistently: user settings, sessions, files, structured information, or interface states. Although the concept of persistence has not changed over the years, the tools, APIs, and best practices have radically changed.
In simple terms, persisting data means that the information survives the closing of the application, configuration changes, and even the restarting of the device. In modern Android, choosing the wrong storage mechanism can lead to data loss, performance problems, or even the rejection of the app on Google Play.
This article analyzes all current techniques for persistent data storage in Android Studio, starting from the classic concepts and bringing them to the current state of Android (Jetpack, Kotlin, and Compose).
In modern versions of Android, incorrectly choosing the storage mechanism can cause:
- Performance problems (main thread blockages)
- Security and privacy risks
- Data loss due to configuration changes
- Rejection of the application on Google Play
In simple terms, persisting data means that the information survives the closing of the app, configuration changes, and, in many cases, the restarting of the device. Current Android no longer relies on a single solution, but on a combined persistence strategy.
In this expanded and updated guide, we will see all the current options for persistent data storage in Android Studio, when to use each one, practical examples, and which approaches have become obsolete.
What is persistence in Android?
In computing, persistence consists of ensuring that the data manipulated by an application survives over time, regardless of the process that created it. Colloquially speaking: that the data is not deleted when the app is closed.
In modern Android, persistence is divided into three main levels, each with a different purpose:
- UI State (memory): temporary data necessary so that the interface does not restart on rotations or configuration changes.
- Local persistence (disk): data that must survive the closing of the app.
- Structured persistence: relational or queryable data.
A well-designed application combines several mechanisms, it does not depend on just one.
Android explicitly recommends not mixing responsibilities. A common mistake in older articles is to use databases or files to save UI state, which is now considered a bad practice.
1. Shared Preferences (SharedPreferences)
SharedPreferences allow you to store simple key-value pairs such as booleans, numbers, or text. Historically, they were the most used mechanism for saving user settings.
Features
- Storage in a private XML file
- Does not require permissions
- Ideal for small configurations
For many years, SharedPreferences was the standard solution for saving simple key-value pairs.
What it allows you to store:
- Boolean
- Int, Long, Float
- String
- Set
Classic example:
Problems detected over time:
- Synchronous writes if
commit()is used - Risk of corruption
- No concurrency control
- No strong typing
Current limitations
- Potential synchronous access
- Risk of inconsistencies
- No type safety
For these reasons, it is no longer recommended as a primary solution in new apps.
1.2 DataStore (modern and recommended replacement)
DataStore is part of Jetpack and is the official replacement for SharedPreferences.
There are two variants:
DataStore Preferences
Ideal for simple settings.
Reactive reading:
Proto DataStore
Used when you need a strong schema and validation.
- Ideal for large apps
- Uses Protobuf
- Avoids errors due to incorrect keys
When to use DataStore:
- User preferences
- Configuration flags
- Small but persistent data
Advantages
- Asynchronous
- Safe from corruption
- Integration with coroutines
- Reactive observation with Flow
When to use DataStore
- User preferences
- Configuration flags
- Simple persistent states
2. File storage
2.1 Internal memory (private and secure)
Internal memory remains one of the safest and simplest options.
val file = File(filesDir, "config.txt")
file.writeText("Hello World")Features:
- Only accessible by the app
- Deleted when uninstalled
- Ideal for sensitive data
Features:
- Does not require permissions
- Only accessible by the app
- Deleted when uninstalled
Example in Kotlin:
Reading:
Typical use cases:
- Configuration files
- Encrypted tokens
- Private app data
2.2 External memory and Scoped Storage
Since Android 10 (API 29), Google introduced Scoped Storage.
What changed:
- Free access to the SD card removed
- WRITE/READ_EXTERNAL_STORAGE permissions deprecated
- Limited access by sandbox
Recommended example:
To access files created by other apps, you must use:
- Storage Access Framework (SAF)
- Intent ACTION_OPEN_DOCUMENT
Important: incorrect use of storage is one of the most common causes of rejection on Google Play.
3. Databases: SQLite vs Room
3.1 Direct SQLite (low level)
SQLite is still the base engine, but handling it manually implies:
- Repetitive code
- Runtime errors
- Difficult maintenance
Today it is considered a low-level option.
Features
- Database in a single file
- Not client-server
- Fast and lightweight
3.2 Room (current standard)
Room is the official abstraction layer of Android Jetpack over SQLite.
Components:
- Entity
- DAO
- Database
Complete example:
Real advantages:
- Compile-time verification
- Integration with Flow and LiveData
- Easy testing
4. Cache: temporary data
The cache allows you to store rebuildable data.
Example:
Good practices:
- Never critical data
- Limit size
- The system can delete it
Good practices:
- Never save critical data
- Limit size
- Clean periodically
5. UI State: ViewModel and SavedState
5.1 ViewModel
Designed to:
- Survive rotations
- Avoid unnecessary reloads
5.2 SavedStateHandle
Allows restoring state after process closure.
Key rule:
UI state should not be saved in the database.
ViewModel
- Keeps data in memory
- Survives rotations
- Does not survive process kill
SavedState / rememberSaveable
- Lightweight backup
- Only small data
6. Advanced persistence: Ink API and serialization
In drawing apps:
- Serialize StrokeInputBatch
- Save Brush separately
- Export to image
This approach is essential for:
- Note-taking apps
- Whiteboards
- Real-time collaboration
Which mechanism should I use?
| Need | Recommended solution |
|---|---|
| Preferences | DataStore |
| UI State | ViewModel + SavedState |
| Relational data | Room |
| Private files | Internal memory |
| Shared files | SAF / Scoped Storage |
| Temporary data | Cache |
Conclusion
Persistence in modern Android is not based on a single technique, but on a well-thought-out architecture:
- ViewModel for UI
- DataStore for preferences
- Room for complex data
- Scoped Storage for files
Updating these concepts is essential to create safe, efficient, and aligned applications with the current standards of Android Studio.
7. End-to-end example: Complete app with ViewModel + DataStore + Room + UI
Scenario
A simple To-Do application that:
- Saves preferences (dark mode)
- Persists tasks in the database
- Maintains UI state on rotations
Architecture
- UI: Jetpack Compose
- State: ViewModel + StateFlow
- Preferences: DataStore
- Data: Room
DataStore – Preferences
Room – Database
ViewModel – State logic
UI – Jetpack Compose
8. 100% Jetpack Compose version
rememberSaveable
It is used for:
- Input text
- Scroll
- Temporary selections
StateFlow + Compose
Advantages:
- Reactive
- Lifecycle-aware
- Easy testing
Room + Flow + Compose
- Room exposes Flow
- ViewModel transforms it
- Compose observes it
This avoids:
- Callbacks
- Inconsistent states
9. Advanced comparison table
| Mechanism | Performance | Survives process kill | Real cases | Common errors |
| ViewModel | Very high | ❌ | UI State | Saving persistent data |
| SavedState | Medium | ✔️ | Inputs, scro |