Bob Rasner
Bob Rasner

Reputation: 283

How do you stop a value saved in Preferences Datastore from resetting when the app is relaunched?

Whenever I relaunch an app, values that are saved in Preferences Datastore are reset.

In particular, I'm trying to save whether the user selected light mode or dark mode for their preferred background theme. However it doesn't maintain when the app is relaunched.

I'm using a boolean to determine the background theme and then save it using Preferences Datastore.

Here is the datastore class that is used to access the value:

import android.content.Context
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

private val Context.dataStore by preferencesDataStore("ThemeDatastore")

class ThemeDatastore(private val context: Context) {
    val themeKey = booleanPreferencesKey("Theme")

    fun loadTheme(key: Preferences.Key<Boolean>): Flow<Boolean> =
        context.dataStore.data.map { preferences ->
            preferences[key] ?: false
        }

    suspend fun saveTheme(key: Preferences.Key<Boolean>, data: Boolean) {
        context.dataStore.edit { preferences ->
            preferences[key] = data
        }
    }
}

The following is the Compose code in Main Activity.

This is the instance of ThemeDatastore. It has the current local context in the context parameter field:

val themeDatastore = ThemeDatastore(LocalContext.current)

Here is the variable that retrieves the preferred background theme. It is initially set to false:

val savedTheme = themeDatastore.loadTheme(key = themeDatastore.themeKey).collectAsState(initial = false)

Here is the boolean variable that determines the background theme. If false, light mode is active, if true, dark mode is active. It is initially set to false:

var isDarkModeActive by remember { mutableStateOf(false) }

Here is the function that calls the function from ThemeDatastore that saves the preferred background theme:

fun saveTheme(){
    CoroutineScope(Dispatchers.IO).launch {
        themeDatastore.saveTheme(themeDatastore.themeKey, isDarkModeActive)
    }
}

Here is the app layout. Is is a box that takes up the entire screen. The background color animates to dark gray if isDarkModeActive and saveTheme.value are both equal to true:

Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
        .background(
            color = animateColorAsState(
                targetValue = if (isDarkModeActive && savedTheme.value)
                    Color.DarkGray else Color.White
            ).value
        )
        .fillMaxSize(),
) {
    Button(onClick = {
        isDarkModeActive = !isDarkModeActive
        saveTheme()
    }) {
        Text(text = "Toggle Theme")
    }
}

In the center of the screen is a button. When the button is pressed, isDarkModeActive is toggled between true and false and the saveTheme function is triggered.

I tried setting the initial value of the savedTheme variable to true. It didn't work.

Upvotes: 2

Views: 111

Answers (1)

tyg
tyg

Reputation: 15763

The DataStore works perfectly fine, it is just that you explicitly set your dark mode to false when your composable is loaded:

var isDarkModeActive by remember { mutableStateOf(false) }

The value of the DataStore doesn't matter, isDarkModeActive will always be false when you restart the app.

You should only have a single source of truth for any given data. In this case that means that you should remove one of the two values that indicate if the dark mode is on:

val themeDatastore = ThemeDatastore(LocalContext.current)
val isDarkModeActive by themeDatastore.loadTheme(key = themeDatastore.themeKey)
    .collectAsStateWithLifecycle(false)
val scope = rememberCoroutineScope()

Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
        .background(
            color = animateColorAsState(
                targetValue = if (isDarkModeActive)
                    Color.DarkGray else Color.White,
            ).value,
        )
        .fillMaxSize(),
) {
    Button(onClick = {
        scope.launch {
            themeDatastore.saveTheme(themeDatastore.themeKey, !isDarkModeActive)
        }
    }) {
        Text(text = "Toggle Theme")
    }
}

Although not necessary to fix this problem, I made two additional changes:

  1. I replaced collectAsState with collectAsStateWithLifecycle from the gradle dependency androidx.lifecycle:lifecycle-runtime-compose to save resources when the lifecycle changes.
  2. I removed the function saveTheme which createad an unmanaged CoroutineScope. Instead, you should retrieve a managed scope with rememberCoroutineScope().

This should now work as intended.

One last thing: The key you pass to loadTheme and saveTheme should be private to ThemeDatastore and directly be used by the functions, not passed as a parameter. There will never be a situation where you would want to use something else than themeDatastore.themeKey.

Upvotes: 1

Related Questions