Search code examples
androidkotlinandroid-jetpack-composelocal-datastore

How can I save the preferences in jetpack compose using preferences datastore?


I have a list of preferences, which i loop over to make a settings screen, and i want to save the state for each of these items, but it isnt saving the state when i restart the app

Code for each function toggle:

@Composable
private fun FunctionToggle(name: String, desc: String, id: String) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    val ds = DataStoreKeys(context)
    val currentState = ds.getDataStoreValue(id).collectAsState(initial = false).value
    var checked by rememberSaveable { mutableStateOf(currentState) }
    ListItem(
        modifier = Modifier.padding(vertical = 4.dp),
        headlineContent = {
            Text(
                text = name,
                style = MaterialTheme.typography.titleLarge,
            )
        },
        supportingContent = {
            Text(
                text = desc,
                style = MaterialTheme.typography.bodyMedium,
            )
        },
        trailingContent = {
            Switch(
                checked = checked == true,
                onCheckedChange = {
                    checked = it
                    scope.launch {
                        ds.saveToDataStore(key = id, value = it)
                    }
                },
            )
        },
    )
class DataStoreKeys(private val context: Context) {
    companion object {
        private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("userSettings")
    }

    fun getDataStoreValue(key: String): Flow<Boolean?> {
        return context.dataStore.data.map { preferences ->
                preferences[booleanPreferencesKey(key)] ?: false
        }
    }

    suspend fun saveToDataStore(key: String, value: Boolean) {
        context.dataStore.edit { preferences ->
            preferences[booleanPreferencesKey(key)] = value
        }
    }
}

Solution

  • The problem is here:

    val currentState = ds.getDataStoreValue(id).collectAsState(initial = false).value
    var checked by rememberSaveable { mutableStateOf(currentState) }
    

    You are collecting the flow from your datastore with default value false and then remembering the value in checked variable. The problem is that you don't update checked when currentState changes, so it always keeps the default value (until you change it from the switch callback directly).

    In my experience, datastore updates are fast enough, so you don't actually need the additional checked variable and you can use the value from datastore directly. If you'd still rather have that possibility to change the value from the switch callback directly, you can do something like this:

    var checked by remember { mutableStateOf(false) }
    
    LaunchedEffect(Unit) {
      ds.getDataStoreValue(id).collect { checked = it }
    }
    
    Switch(
      checked = checked,
      onCheckedChange = {
        checked = it
        scope.launch {
          ds.saveToDataStore(key = id, value = it)
        }
      },
    )
    

    With this, checked gets updated every time datastore has a new value as well.