Preferences DataStore est une solution de stockage de données qui vous permet de stocker assez facilement de petites quantité de données dans la mémoire permanente. Les donnés sont stockées sous forme de clé-valeur. Les clés permettent ensuite de retrouver les données. DataStore utilise les coroutines Kotlin et Flow pour stocker les données de manière asynchrone. Preferences Datastore est idéal pour de petites quantités de données comme les préférences utilisateur. Pour des volumes de données plus importants, il est recommandé d'utiliser une base de données comme Room.
Pour pouvoir utiliser la librairie du datastore, il faut quelques ajustement dans les fichiers du gradle:
Dans cet exemple, on va réaliser une implémentation très simple pour tester le datastore
package package com.example.pdatastore1
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
val Context.dataStore by preferencesDataStore(name = "settings")
val key = stringPreferencesKey("STR_KEY")
@Composable
fun MyApp() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
var textToSave by remember { mutableStateOf("") }
var savedText by remember { mutableStateOf("") }
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
OutlinedTextField(
value = textToSave,
onValueChange = { textToSave = it },
)
Button(onClick = {
scope.launch {
writeString(context, textToSave)
}
}) {
Text(text = "Sauvegarder")
}
Button(onClick = {
scope.launch {
savedText = readString(context).first()
}
}) {
Text(text = "Lire")
}
OutlinedTextField(
value = savedText,
onValueChange = { savedText = it }
)
}
}
suspend fun writeString(context: Context, value: String) {
context.dataStore.edit { preferences ->
preferences[key] = value
}
}
fun readString(context: Context): Flow<String> {
return context.dataStore.data.map { preferences ->
preferences[key] ?: ""
}
}
//fun readString(context: Context) = context.dataStore.data.map { it[key] ?: "" }
val Context.dataStore by preferencesDataStore(name = "settings")
On déclare un DataStore nommé "settings" associé au contexte de l'application. Il est déclaré
en globale pour être accessible dans les fonctions writeString et readString.
val key = stringPreferencesKey("STR_KEY")
Définit une clé unique pour identifier les données dans DataStore
suspend fun writeString(context: Context, value: String) {
context.dataStore.edit { preferences ->
preferences[key] = value
}
}
Cette fonction permet de sauvegarder une chaîne dans le datastore. C'est une fonction de type
suspend, elle suspend momentanément l'exécution du reste de l'application. Il faut
l'appeler dans une coroutine d'où la variable scope déclarée dans MyApp()
scope.launch {
writeString(context, textToSave)
}
fun readString(context: Context): Flow<String> {
return context.dataStore.data.map { preferences ->
preferences[key] ?: ""
}
}
Cette fonction permet de lire une chaîne dans le datastore. Elle n'est pas de type
suspend, on n'est pas obligé de l'appeler dans une coroutine, nous l'avons tout de
même fait, car cette fonction retourne un flux de données (Flow<String>), et la
méthode .first() qui permet de récupérer le premier élément du flux doit être exécutée
dans une coroutine
scope.launch {
savedText = readString(context).first()
}
fun readString(context: Context) = context.dataStore.data.map { it[key] ?: "" }
Nous allons reprendre le même exemple que précédemment, mais cette fois on va placer la gestion du datastore dans un fichier à part. C'est comme ça que l'on procédera dorénavant.
package com.example.pdatastore2.data
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private val Context.dataStore by preferencesDataStore("PDSEX2")
data class PDSmanager(private val context: Context){
suspend fun writeString(strkey: String, value: String) {
val key = stringPreferencesKey(strkey)
context.dataStore.edit { preferences ->
preferences[key] = value
}
}
fun readString(strkey: String,): Flow<String> {
val key = stringPreferencesKey(strkey)
return context.dataStore.data.map { preferences ->
preferences[key] ?: ""
}
}
}
package com.example.pdatastore2
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.pdatastore2.data.PDSmanager
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
@Composable
fun MyApp() {
val myPDSmanager = PDSmanager(LocalContext.current)
val coroutineScope = rememberCoroutineScope()
var textToSave by remember { mutableStateOf("") }
var savedText by remember { mutableStateOf("") }
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
OutlinedTextField(
value = textToSave,
onValueChange = { textToSave = it },
)
Button(onClick = {
coroutineScope.launch {
myPDSmanager.writeString("KEY2", textToSave)
}
}) {
Text(text = "Sauvegarder")
}
Button(onClick = {
coroutineScope.launch {
savedText = myPDSmanager.readString("KEY2").first()
}
}) {
Text(text = "Lire")
}
OutlinedTextField(
value = savedText,
onValueChange = { savedText = it },
)
}
}
On va reprendre l'exemple précédent. On va supprimer le bouton Lire. On va faire de sorte que la donnée sauvegardée soit actualisée automatiquement dans l'application dès son changement dans le datastore
package com.example.pdatastore3
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.pdatastore3.data.PDSmanager
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
@Composable
fun MyApp() {
val myPDSmanager = PDSmanager(LocalContext.current)
val coroutineScope = rememberCoroutineScope()
var textToSave by remember { mutableStateOf("") }
val savedText by myPDSmanager.readString("KEY3").collectAsState(initial = "")
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
OutlinedTextField(
value = textToSave,
onValueChange = { textToSave = it },
)
Button(onClick = {
coroutineScope.launch {
myPDSmanager.writeString("KEY3", textToSave)
}
}) {
Text(text = "Sauvegarder")
}
Text(text =savedText,
modifier = Modifier
.size(300.dp, 50.dp)
.border( width=2.dp, Color.Blue, shape = RoundedCornerShape(5.dp) )
.wrapContentHeight(Alignment.CenterVertically)
.padding(start = 10.dp)
)
}
}