Reputation: 283
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
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:
collectAsState
with collectAsStateWithLifecycle
from the gradle dependency androidx.lifecycle:lifecycle-runtime-compose
to save resources when the lifecycle changes.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