La mayoría de aplicaciones necesitan persistir información en el tiempo, ya sea ajustes de configuración o preferencias de usuario. Es por ello que hoy aprenderemos a implementar persistencia de datos en Kotlin Multiplatform de una manera muy sencilla gracias a Multiplatform-Settings.

Añadiendo persistencia de datos con Multiplatform Settings

Para este ejemplo utilizaremos la app NavigationExampleKMP que tenéis en el repositorio del curso de Kotlin Multiplatform.

Lo primero que debemos entender es que al trabajar con multiplataforma, la persistencia de datos que utilizará cada plataforma será distinta, por ejemplo en Android SharedPreferences y en iOS NSUserDefault. Esto hace que la implementación de la persistencia sea complicada si no utilizamos una buena librería.

Multiplatform Settings es una poderosa librería que lo simplifica TODO. Ya que nos permitirá implementar su dependencia de dos formas. Si queremos una implementación básica o si por el contrario necesitamos acceder a la persistencia de cada plataforma a más bajo nivel. Mi recomendación es utilizar la básica que es la de este ejemplo.

Para añadir al dependencia tendremos que seguir utilizando Version Catalog, para ello iremos al fichero libs.versions.toml

[versions]
//Otras versiones
settings-multiplatform = "1.1.1"

[libraries]
//Otras librerías
settings = {module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "settings-multiplatform"}

Sincronizamos y entramos en el fichero build.gradle.kts, donde tendremos que ir al bloque commonMain.dependencies{} y añadir nuestra implementación.

commonMain.dependencies {
   //Otras implementaciones
    implementation(libs.settings)
}

Recuerda que tienes el código completo en GitHub.

Diseñando las pantallas

Para este tutorial vamos a necesitar navegar entre pantallas, ya que la idea es añadir información en una pantalla, guardar en base de datos y recuperarla en la siguiente, por lo que si quieres seguir el tutorial al 100% te recomiendo que te veas los capítulos anteriores de navegación.

Lo primero que haremos será ir a MainScreen, pantalla que creamos en los capítulos anteriores y utilizábamos para ir a distintos tutoriales, por lo que crearemos otro botón para navegar a la parte de persistencia.

Spacer(Modifier.height(18.dp))
Button(onClick = {
    navigator.push(ProfileScreen())
}) {
    Text("Navegación con persistencia")
}

Creando ProfileScreen

Esta pantalla dispondrá de una columna con un TextField, un CheckBox y un botón de continuar.

class ProfileScreen : Screen {
    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        var name by remember { mutableStateOf("") }
        var isVip by remember { mutableStateOf(false) }

        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Spacer(Modifier.weight(1f))
            OutlinedTextField(value = name, onValueChange = { name = it })
            Row(verticalAlignment = Alignment.CenterVertically) {
                Checkbox(checked = isVip, onCheckedChange = { isVip = it })
                Text("Eres VIP?")
            }
            Spacer(Modifier.weight(1f))
            Button(onClick = {
                navigator.push(ProfileResultScreen())
            }, enabled = name.isNotEmpty()) {
                Text("Guardar perfil")
            }
            Spacer(Modifier.weight(0.3f))
        }
    }

}

Poco que explicar del código anterior, le he puesto unos Spacers para que tengan espacios entre los componentes y utilizamos un OutlinedTextField para que quede mas bonito, pero la lógica es muy sencilla.

También hay que fijarse que el botón de continuar lo que hace es navegar a ProfileResultScreen (pantalla que todavía no existe) y además solo estará habilitado cuando el nombre del usuario no sea vacío.

Para añadir la persistencia de datos solo tendremos que crear una variable Settings.

private val settings: Settings = Settings()

Ahora podemos llamar a Settings donde queramos y podremos guardar información a modo de clave-valor.

Vamos a volver al botón de continuar y antes de navegar lo que haremos será persistir el nombre y si es Vip.

settings[KEY_NAME] = name
settings[KEY_VIP] = isVip

Las variables KEY_NAME y KEY_VIP son la clave que yo suelo añadir en la parte superior de la clase en un Companion Object.

companion object{
    const val KEY_NAME = "NAME"
    const val KEY_VIP = "VIP"
}

Creando ProfileResultScreen

La siguiente pantalla será muy sencilla, pero antes de ponernos manos a la obra hay que ver como recuperar los valores que hemos persistido.

Para acceder a la base de datos basta con crear otra variable Settings, la cual nos permitirá recuperar valores de la siguiente manera.

settings.getBoolean(KEY_VIP, false)
settings.getString(KEY_NAME, "")

Lo único que necesitamos es la clave, que la teníamos en el Companion Object de la clase anterior. Además le estamos pasando un segundo parámetro que actúa como valor por defecto por si no encuentra ningún valor en la base de datos.

Ahora lo que haremos será una columna con un color de fondo distinto dependiendo de si es vip o no y un texto que te salude con el nombre.

class ProfileResultScreen : Screen {
    private val settings: Settings = Settings()

    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        val isVip = settings.getBoolean(KEY_VIP, false)
        val backgroundColor = if (isVip) {
            Color.Yellow
        } else {
            Color.White
        }

        Column(
            modifier = Modifier.fillMaxSize().background(backgroundColor),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            val name = settings.getString(KEY_NAME, "")
            Text("Bienvenid@ $name", fontSize = 26.sp, fontWeight = FontWeight.Bold)
            Button(onClick = {
//                settings.remove(KEY_NAME)
//                settings.remove(KEY_VIP)
                settings.clear()
                navigator.pop()
            }) {
                Text("Volver y borrar datos")
            }
        }
    }
}

Como comentaba simplemente tenemos una variable backgroundColor que dependiendo de si es true o false será amarillo o blanco. Esa variable se la asignamos al color de fondo de la columna.

También hay que destacar que al pulsar el botón de volver estamos borrando TODA la base de datos con la función clear(), si solo quisiéramos borrar algún campo podemos usar la función comentada remove(CLAVE).

Y ya tenemos nuestra aplicación funcionando con persistencia de datos.

Recuerda que puedes descargar TODO el proyecto desde GitHub de manera gratuita (y me ayudas mucho dándole una estrella ⭐️).


Si te ha gustado puedes seguirme en mis redes sociales en Aristi.Dev. Y si tienes dudas con este o cualquier otro artículo del blog únete al Discord de la comunidad y te ayudaremos.