Prefiere el almacenamiento de datos con Jetpack DataStore

Publicado por Florina Muntenescu, Defensor del desarrollador de Android, Rohit Sathyanarayana, Ingeniero de software

Bienvenido Jetpack DataStore, ahora en versión alfa, una nueva y mejorada solución de almacenamiento de datos destinada a reemplazar las preferencias compartidas. Basado en la co-rutina de Kotlin y Flow, DataStore proporciona dos implementaciones diferentes: Proto DataStore, que te permite archivar objetos escritos (soportado por búfer de protocolo) e Preferencias de DataStore, que almacena pares clave-valor. Los datos se almacenan de forma asincrónica, coherente y transaccional, superando la mayoría de las desventajas de SharedPreferences.

SharedPreferences vs DataStore

Preferencias compartidas

* SharedPreferences tiene una API síncrona que puede parecer segura para llamar en el hilo de la interfaz de usuario, pero que en realidad realiza operaciones de E / S de disco. Además, apply() bloquea el hilo de la interfaz de usuario fsync(). esperando fsync() las llamadas se activan cada vez que se inicia o detiene un servicio y cada vez que se inicia o se detiene una actividad en cualquier lugar de la aplicación. El hilo de la interfaz de usuario está atascado pendiente fsync() llamadas programadas desde apply(), convirtiéndose a menudo en una fuente de ANR.

** SharedPreferences genera errores de análisis como excepciones en tiempo de ejecución.

En ambas implementaciones, el DataStore guarda las preferencias en un archivo y realiza todas las operaciones sobre los datos en Dispatchers.IO a menos que se especifique lo contrario.

Aunque tanto Preferences DataStore como Proto DataStore permiten guardar datos, lo hacen de diferentes formas:

  • Preferencia de DataStore, como SharedPreferences, no tiene forma de definir un esquema o garantizar que se acceda a las claves con el tipo correcto.
  • Proto DataStore le permite definir un esquema utilizando búferes de protocolo. El uso de Protobufs permite la persistencia datos fuertemente tipados. Son más rápidos, más pequeños, más simples y menos ambiguos que XML y otros formatos de datos similares. Aunque Proto DataStore requiere aprender un nuevo mecanismo de serialización, creemos que la ventaja del esquema fuertemente tipado que ofrece Proto DataStore vale la pena.

Habitación vs DataStore

Si necesita actualizaciones parciales, integridad referencial o soporte para conjuntos de datos grandes / complejos, debería considerar usar Room en lugar de DataStore. DataStore es ideal para conjuntos de datos pequeños y simples y no admite actualizaciones parciales ni integridad referencial.

Comience agregando la dependencia de DataStore. Si está utilizando Proto DataStore, asegúrese de agregar también la dependencia de proto:

// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"


// Proto DataStore
implementation  "androidx.datastore:datastore-core:1.0.0-alpha01"

Cuando trabaja con Proto DataStore, define su esquema en un proto en app/src/main/proto/ directorio. Consulte la Guía de lenguaje Protobuf para obtener más información sobre cómo definir un esquema protobuf.

syntax = "proto3";

option java_package = "<your package name here>";
option java_multiple_files = true;

message Settings {
  int my_counter = 1;
}

Crear el almacén de datos

Crea el archivo DataStore con el Context.createDataStore() funciones de extensión.

// with Preferences DataStore
val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings"
)

Si está utilizando Proto DataStore, también deberá implementar el archivo Serializer para indicarle al DataStore cómo leer y escribir el tipo de datos.

object SettingsSerializer : Serializer<Settings> {
    override fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}


// with Proto DataStore
val settingsDataStore: DataStore<Settings> = context.createDataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)

Leer datos de DataStore

DataStore expone los datos almacenados en un archivo Flow, ambos en un archivo Preferences object o como el objeto definido en el protoesquema. DataStore garantiza que los datos se recuperen en Dispatchers.IO por lo tanto, el hilo de la interfaz de usuario no está bloqueado.

Con las preferencias de DataStore:

val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
     .map { currentPreferences ->
        // Unlike Proto DataStore, there's no type safety here.
        currentPreferences[MY_COUNTER] ?: 0   
   }

Con Proto DataStore:

val myCounterFlow: Flow<Int> = settingsDataStore.data
    .map { settings ->
        // The myCounter property is generated for you from your proto schema!
        settings.myCounter 
    }

Escribir datos en DataStore

Para escribir los datos, DataStore ofrece una retención DataStore.updateData() función que proporciona el estado actual de los datos almacenados como parámetro, tanto como archivo Preferences objeto o una instancia del objeto definido en el protoesquema. los updateData() La función actualiza los datos a nivel transaccional en una operación atómica de lectura-escritura-modificación. La corrutina se completa una vez que los datos se conservan en el disco.

Preferencias de DataStore también proporciona una DataStore.edit() función para facilitar la actualización de datos. En lugar de recibir un Preferences artículo, recibe un MutablePreferences objeto que edita. Al igual que con updateData(), los cambios se aplican al disco después de que se completa el bloque de transformación y la corrutina se completa una vez que los datos se mantienen en el disco.

Con las preferencias de DataStore:

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        // We can safely increment our counter without losing data due to races!
        val currentCounterValue = settings[MY_COUNTER] ?: 0
        settings[MY_COUNTER] = currentCounterValue + 1
    }
}

Con Proto DataStore:

suspend fun incrementCounter() {
    settingsDataStore.updateData { currentSettings ->
        // We can safely increment our counter without losing data due to races!
        currentSettings.toBuilder()
            .setMyCounter(currentSettings.myCounter + 1)
            .build()
    }
}

Migración de SharedPreferences a DataStore

Para migrar de SharedPreferences a DataStore, debe pasar un archivo SharedPreferencesMigration objeto al constructor DataStore. DataStore puede migrar automáticamente de SharedPreferences a DataStore por usted. Las migraciones se realizan antes de que pueda ocurrir cualquier acceso a los datos en el DataStore. Esto significa que la migración debe haber tenido éxito primero. DataStore.data devuelve cualquier valor y antes DataStore.updateData() puede actualizar los datos.

Si está migrando a Preferences DataStore, puede utilizar el SharedPreferencesMigration implementación y simplemente pase el nombre usado para construir sus SharedPreferences.

Con las preferencias de DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)

Al migrar a Proto DataStore, deberá implementar una función de mapeo que defina cómo migrar desde los pares clave-valor utilizados por SharedPreferences al esquema de DataStore definido.

Con Proto DataStore:

val settingsDataStore: DataStore<Settings> = context.createDataStore(
    produceFile = { File(context.filesDir, "settings.preferences_pb") },
    serializer = SettingsSerializer,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "settings_preferences"            
        ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
            // Map your sharedPrefs to your type here
          }
    )
)

SharedPreferences tiene varios inconvenientes: una API síncrona que puede parecer segura para llamar en el hilo de la interfaz de usuario, ningún mecanismo para informar errores, falta de API transaccional y más. DataStore es un reemplazo de SharedPreferences que soluciona la mayoría de estas deficiencias. DataStore incluye una API totalmente asincrónica que utiliza las corrutinas Kotlin y Flow, administra la migración de datos, garantiza la coherencia de los datos y maneja la corrupción de datos.

Dado que DataStore todavía está en versión alfa, ¡necesitamos su ayuda para mejorarlo! Para comenzar, aprenda más sobre DataStore en nuestra documentación y pruébelo usando nuestros laboratorios de código: el laboratorio de códigos DataStore y las preferencias del laboratorio de códigos Proto DataStore. Entonces, háganos saber cómo podemos mejorar la biblioteca creando problemas en Issue Tracker.



Compruebe también

en vivo desde Droidcon, incluida la mayor actualización de Gemini en Android Studio y más lanzamientos del SDK de Android.

Acabamos de lanzar nuestro episodio de otoño de #TheAndroidShow en YouTube etcétera desarrollador.android.comy esta vez …

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *