在 Jetpack Compose 中将深色主题保存到数据存储后,屏幕在启动时闪烁

Screen flashes on launch after saving dark theme to dataStore in jetpack compose

我有一个简单的 Light/Dark 主题切换,我正在将主题保存到数据存储中。 保存主题并关闭应用程序后,屏幕会暂时以浅色模式启动,然后切换到深色主题。我错过了什么吗?这是我在这里处理的副作用吗?请指教

这是解释案例的 GIF https://i.stack.imgur.com/7YH7z.gif


这是 theme.kt 文件的代码

private val DarkColorPalette = darkColors(
    primary = White100,
)

private val LightColorPalette = lightColors(
    primary = Blue100,
)

@Composable
fun MyTheme(
    darkTheme: MutableState<Boolean>,
    content: @Composable () -> Unit) {

    val colors = if (darkTheme.value) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

这是 MainActivity.kt 文件的代码

// DataStore
val Context.dataStore: DataStore<Preferences> by preferencesDataStore("settings")

// DataStore key
val USER_THEME = booleanPreferencesKey("user_theme")

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        // Reading theme
        val themeFlow: Flow<Boolean> = dataStore.data
            .map { preferences ->
                preferences[USER_THEME] ?: false
            }

        // Toggle and save theme
        suspend fun toggleTheme() {
            dataStore.edit { settings ->
                val currentTheme = settings[USER_THEME] ?: false
                settings[USER_THEME] = !currentTheme
            }
        }

        super.onCreate(savedInstanceState)
        setContent {

            // Getting the theme from DataStore
            // If the initial value is true, it works fine on dark mode but not on Light
            // If the initial value is false, it works fine on light mode but not on dark
            val userTheme = themeFlow.collectAsState(initial = false)

            // Mutable state of the theme to be passed into "My Theme"
            val themeState: MutableState<Boolean> = mutableStateOf(userTheme.value)

            // Scope to toggle theme on click
            val scope = rememberCoroutineScope()

            // UI
            MyTheme (darkTheme = themeState) {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Image(
                        painter = painterResource(id = R.drawable.icon_dark),
                        contentDescription = "Theme Switcher",
                        colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
                        modifier = Modifier
                            .clickable {
                                scope.launch {
                                    toggleTheme()
                                }
                            }
                    )
                }

            }
        }
    }
}


在 Francesc 回答后编辑代码:

  1. 删除了“userTheme”行并向该行添加了 runBlocking
val themeState: MutableState<Boolean> = runBlocking {  mutableStateOf(themeFlow.first())}
  1. 通过按钮手动切换主题值:
scope.launch {
themeState.value = !themeState.value
saveTheme() // renamed it to saveTheme instead of toggleTheme
}

您的主题是从商店异步读取的,最初它默认为 false(不是深色主题),因此它以浅色模式显示。稍后当流发出并且模式为暗模式时,UI 切换到暗模式。

由于数据存储的异步性,您有 2 个选择:

  • 同步读取主题初始值,使用runBlocking
  • 在等待读取时显示一些占位符,可能是 Box 带有您的应用程序徽标(就像启动画面),或者什么都不显示,空白 canvas