there is a mainActivity in which the navHost is called and the drawer is called in it, and from it the settings screen
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
setContent {
val viewModel: SettingsViewModel = hiltViewModel()
val activityState by viewModel.appTheme.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.getTheme(this@MainActivity)
}
AppTheme(
isDarkTheme = shouldUseDarkTheme(appTheme = activityState)
) {
TopNavHost()
}
}
}
}
viewModel for settings:
class SettingsViewModel: ViewModel() {
private val _appTheme = MutableStateFlow(AppTheme.System)
val appTheme: StateFlow<AppTheme> = _appTheme
fun getTheme(context: Context) {
viewModelScope.launch {
_appTheme.value = withContext(Dispatchers.IO) {
DataStoreManager(context).getTheme()
}
}
}
fun saveTheme(context: Context, newTheme: AppTheme) {
_appTheme.value = newTheme
viewModelScope.launch {
DataStoreManager(context).saveTheme(_appTheme.value.name)
}
}
}
and the settings screen itself
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
onBack: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel()
) {
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.getTheme(context)
}
Scaffold(topBar = {
TopAppBar(
title = {
Text(
text = stringResource(id = R.string.settings),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
IconButton(onClick = { onBack() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back"
)
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 10.dp)
) {
ThemeSettings(viewModel.appTheme.collectAsStateWithLifecycle().value) {
viewModel.saveTheme(context, it)
}
HorizontalDivider(
modifier = Modifier.padding(top = 5.dp, bottom = 10.dp),
thickness = 1.dp
)
LanguageSettings()
HorizontalDivider(
modifier = Modifier.padding(top = 5.dp, bottom = 10.dp),
thickness = 1.dp
)
}
})
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ThemeSettings(appTheme: AppTheme, onAppThemeChanged: (AppTheme) -> Unit) {
val themes = arrayOf(
stringResource(id = R.string.system_theme),
stringResource(id = R.string.dark_theme),
stringResource(id = R.string.light_theme)
)
Column(
modifier = Modifier.fillMaxWidth()
) {
Spacer(modifier = Modifier.height(5.dp))
Text(text = stringResource(id = R.string.theme))
FlowRow(
modifier = Modifier.fillMaxWidth()
) {
TextedRadioButton(
selected = appTheme == AppTheme.System,
onClick = {
if (appTheme != AppTheme.System)
onAppThemeChanged(AppTheme.System)
},
text = themes[0]
)
TextedRadioButton(
selected = appTheme == AppTheme.Light,
onClick = {
if (appTheme != AppTheme.Light)
onAppThemeChanged(AppTheme.Light)
},
text = themes[2]
)
TextedRadioButton(
selected = appTheme == AppTheme.Dark,
onClick = {
if (appTheme != AppTheme.Dark)
onAppThemeChanged(AppTheme.Dark)
},
text = themes[1]
)
}
}
}
If I call settingsScreen directly from MainActivity then the theme changes without re-entering the application. I understand that my MainActivity only starts when the application is turned on, but I don’t understand how I can change the theme directly from the settings screen.
here is the theme code:
@Composable
fun AppTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (isDarkTheme) DarkColors else LightColors
if (Build.VERSION.SDK_INT >= 29) {
LocalView.current.isForceDarkAllowed = false
}
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = !isDarkTheme
)
systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = !isDarkTheme
)
}
MaterialTheme(
colorScheme = colorScheme,
content = {
Surface(
modifier = Modifier.fillMaxSize(),
color = colorScheme.background,
contentColor = colorScheme.onBackground,
content = content
)
}
)
}
With code you provided it looks like there are 2 causes:
1. You are using two instances of settingsViewModel
In your code
_appTheme.value = newThemeis called only forSettingsViewModelinstance onSettingsScreen, the other one (insideMainActivity) remains unchenged.There is nothing wrong about it but your
DataStoreManagershould provide way how to share singleAppThemeon multiple screens.2. You don't observing changes for your
AppTheme.In your code are getting
AppThemewithinLaunchedEffectwhich is single time operation (in your code, not in general). That's why it's working only when you enter the app again.Solution
I don't know your
DataStoreManagerso i created this one. Usinghilt@Singletonto make sure you have only one instance.With this, you can collect
AppThemechanges in yourSettingsViewModeland send it further. There is no need forgetThemefunction now, theme is being updated.With this, your theme change will be visible immediately.