DataStore es un componente de la librería Jetpack diseñado para almacenar pequeñas cantidades de datos en aplicaciones Android. A diferencia de SharedPreferences, funciona de forma asíncrona y garantiza la consistencia de los datos bajo acceso concurrente. Según Google, 2024, DataStore usa Kotlin Coroutines y Flow, lo que lo hace seguro para el hilo principal y adecuado para arquitecturas reactivas.
Puntos Clave
DataStore es una solución de Google para el almacenamiento local de datos en Android, presentada en 2020 como alternativa a SharedPreferences. Soporta dos modos: Preferences DataStore (pares simples clave-valor) y Proto DataStore (esquema tipado basado en Protocol Buffers).
La principal ventaja es la asincronía total: todas las operaciones de lectura devuelven un Flow de Kotlin Coroutines, y las escrituras se realizan en un contexto de corrutina. Esto elimina el bloqueo del hilo principal, que era un problema típico de SharedPreferences al manejar grandes volúmenes de datos.
DataStore garantiza la atomicidad de las operaciones: las escrituras concurrentes no provocan pérdida de datos gracias a su modelo transaccional. Si dos componentes modifican el mismo valor simultáneamente, DataStore maneja correctamente el conflicto mediante un mecanismo de compare-and-swap.
Según Google I/O 2023, DataStore se usa en el 40% de los nuevos proyectos Android, y Google recomienda migrar desde SharedPreferences en todas las aplicaciones donde se requiera estabilidad en el almacenamiento de configuraciones.
En el núcleo de DataStore se encuentra SingleProcessDataStore — una implementación que opera dentro de un solo proceso. Utiliza almacenamiento basado en archivos con bloqueo a nivel de archivo: al escribir datos, el archivo se bloquea, evitando la corrupción bajo acceso concurrente.
DataStore maneja automáticamente los errores de deserialización: si el archivo está dañado, devuelve un valor por defecto y sobrescribe el archivo. Este comportamiento es configurable mediante corruptionHandler, que se puede establecer al crear el DataStore.
SharedPreferences sufre de tres problemas fundamentales: lectura síncrona del disco en el hilo principal, falta de garantías de atomicidad para escrituras concurrentes e imposibilidad de rastrear cambios de forma reactiva. DataStore resuelve los tres: Flow para observación, bloqueo de archivos para atomicidad y API asíncrona para seguridad de hilos.
DataStore almacena datos en archivos en el almacenamiento interno del dispositivo. Preferences DataStore usa un formato de archivo similar a SharedPreferences pero con metadatos adicionales para verificación de integridad. Proto DataStore usa el formato binario Protocol Buffers, lo que reduce el tamaño del archivo y acelera la serialización.
Al leer datos, DataStore carga todo el archivo en memoria una vez, tras lo cual los suscriptores reciben el estado actual mediante Flow. Los cambios se transmiten a todos los suscriptores activos automáticamente — no se requiere registro manual de listeners, como en SharedPreferences.
Preferences DataStore utiliza un mecanismo de serialización incorporado basado en un Map. Cada entrada es un par de una cadena y un tipo primitivo (Int, Boolean, Float, Long, String, Set). Los datos se almacenan en un archivo XML, similar a SharedPreferences, pero con escritura atómica mediante bloqueo de archivos.
Ejemplo de creación de Preferences DataStore: la extensión preferencesDataStore en Context crea un singleton con el nombre del archivo. En llamadas repetidas, se devuelve la misma instancia — esto elimina la duplicación de archivos y la confusión entre diferentes instancias de almacenamiento.
Proto DataStore requiere definir un esquema de datos mediante un archivo .proto y compilarlo con el plugin protobuf. La clase Java generada se usa como único punto de entrada para todos los campos — esto elimina errores tipográficos en las claves, comunes con SharedPreferences.
El esquema de Proto DataStore se define una vez y soporta la adición de nuevos campos sin perder datos antiguos. Si una nueva versión de la aplicación añade un campo con un valor por defecto, el archivo antiguo se deserializará correctamente — la compatibilidad hacia atrás está integrada en el protocolo.
La elección entre Preferences DataStore y Proto DataStore depende de la complejidad de los datos y los requisitos de tipificación. Ambas opciones son asíncronas y transaccionales, pero difieren en seguridad de tipos y rendimiento de serialización.
| Característica | Preferences DataStore | Proto DataStore |
|---|---|---|
| Tipificación | Débil (clave-valor) | Fuerte (clase generada) |
| Serialización | XML (incorporada) | Protocol Buffers (protobuf) |
| Tamaño de archivo | Grande (XML legible) | Pequeño (binario) |
| Complejidad | Baja (sin .proto) | Media (requiere .proto) |
| Migración de esquema | Sin esquema | Automática (proto) |
| Compatibilidad | SharedPreferences (vía migración) | Solo Proto DataStore |
Preferences DataStore es adecuado para configuraciones simples: banderas de funciones, cadena de token de autorización, número de inicios de la aplicación. Si los datos son pocos (hasta 10–15 claves) y no requieren un esquema estricto, Preferences DataStore proporciona un umbral de entrada mínimo sin conectar el plugin protobuf.
Proto DataStore se justifica cuando la estructura de datos es compleja o puede cambiar entre versiones de la aplicación. Por ejemplo, configuración de perfil de usuario o configuración de pruebas A/B con 20+ campos. Protobuf proporciona tipificación fuerte y migraciones automáticas, eliminando errores en tiempo de ejecución por discrepancias de claves.
Google proporciona un mecanismo de migración incorporado a través de la clase SharedPreferencesMigration. La migración se realiza una vez en el primer inicio después de la actualización de la aplicación: DataStore lee los datos de SharedPreferences, los escribe en su propio formato y marca la migración como completada.
La migración soporta transformaciones personalizadas: si las claves en SharedPreferences no coinciden con las claves deseadas de DataStore, se puede especificar una función de transformación mediante SharedPreferencesMigration. Esto permite renombrar claves y cambiar tipos de datos durante la migración.
Primero, añade DataStore a build.gradle y crea una instancia de DataStore con migración: SharedPreferencesMigration acepta el nombre del archivo SharedPreferences y un conjunto de claves a transferir. Segundo, elimina todo el código que funcione con SharedPreferences y reemplázalo con llamadas a DataStore. Tercero, prueba la migración: en el primer inicio, los datos deberían aparecer en DataStore y el archivo SharedPreferences antiguo ya no debería usarse.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore se integra fácilmente en un proyecto existente. A continuación se muestran ejemplos prácticos para Preferences DataStore y Proto DataStore — ambos demuestran lectura, escritura y observación reactiva de datos.
En este ejemplo, Preferences DataStore almacena tres configuraciones: tema oscuro, nombre de usuario y contador de inicios. La lectura se realiza mediante la extensión .data, que devuelve un Flow. La escritura se realiza mediante la función suspend .edit, que garantiza la atomicidad de los cambios.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore requiere definir un archivo .proto. Después de la compilación, se crea una clase UserSettings que se utiliza para lectura y escritura. Las migraciones de versión del esquema se describen en el mismo archivo .proto y se aplican automáticamente.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Lectura desde DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Escritura de nuevos valores
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore se integra con la arquitectura MVVM a través de ViewModel. El Flow de DataStore se recoge mediante .stateIn y se usa en la UI. Con cada cambio de datos, la UI se actualiza automáticamente — no se necesitan actualizaciones manuales ni LiveData.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Preguntas Frecuentes
DataStore funciona de forma asíncrona (no bloquea el hilo UI), soporta acceso concurrente mediante transacciones y permite la suscripción reactiva a cambios a través de Flow. SharedPreferences es una API síncrona con riesgo de ANR con grandes volúmenes de datos y sin soporte integrado de reactividad.
DataStore está escrito en Kotlin y requiere Kotlin Coroutines. Usarlo desde Java es posible pero incómodo: habría que crear envoltorios con CompletableFuture o gestionar las corrutinas manualmente. Para proyectos Java, Google recomienda mantener SharedPreferences o añadir Kotlin al módulo.
DataStore carga todo el archivo en memoria al leer, por lo que no es adecuado para almacenar listas u objetos grandes. Para tales escenarios, usa Room o SQLite. DataStore está optimizado para configuraciones y datos estructurados pequeños — hasta cientos de kilobytes.
Al crear un DataStore, puedes pasar un corruptionHandler — una función que se llama cuando el archivo está dañado. Por defecto, DataStore lanza una excepción CorruptionException. En el corruptionHandler, puedes devolver datos vacíos, tras lo cual DataStore sobrescribirá el archivo con un estado correcto.
Sí, Proto DataStore requiere definir un esquema en un archivo .proto y conectar el protobuf-gradle-plugin. Si el proyecto es pequeño y los datos son simples, es más fácil usar Preferences DataStore — no requiere configuración adicional de compilación.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.