Initially, the theme of my application followed the theme of the system, for convenience I created drawable resources for light and dark themes. Then I implemented the ability to change the theme manually regardless of the system theme, but apparently the drawable resources are tied to the system theme or something else. Is there a way to force-switch between night and notnight drawable resources somehow?
CodePudding user response:
What you can do is use Data Store and assign the value of that toggle to it. Then in your setContent {}
you can check it as :
val theme = userSettings.themeStream.collectAsState()
val useDarkColors = when (theme.value) {
AppTheme.MODE_AUTO -> isSystemInDarkTheme()
AppTheme.MODE_DAY -> false
AppTheme.MODE_NIGHT -> true
}
Where userSettings is :
enum class AppTheme {
MODE_DAY,
MODE_NIGHT,
MODE_AUTO;
companion object {
fun fromOrdinal(ordinal: Int) = values()[ordinal]
}
}
interface UserSettings {
val themeStream: StateFlow<AppTheme>
var theme: AppTheme
}
class UserSettingsImpl @Inject constructor(
@ApplicationContext context: Context
) : UserSettings {
override val themeStream: MutableStateFlow<AppTheme>
override var theme: AppTheme by AppThemePreferenceDelegate("app_theme", AppTheme.MODE_AUTO)
private val preferences: SharedPreferences =
context.getSharedPreferences("sample_theme", Context.MODE_PRIVATE)
init {
themeStream = MutableStateFlow(theme)
}
inner class AppThemePreferenceDelegate(
private val name: String,
private val default: AppTheme,
) : ReadWriteProperty<Any?, AppTheme> {
override fun getValue(thisRef: Any?, property: KProperty<*>): AppTheme =
AppTheme.fromOrdinal(preferences.getInt(name, default.ordinal))
override fun setValue(thisRef: Any?, property: KProperty<*>, value: AppTheme) {
themeStream.value = value
preferences.edit {
putInt(name, value.ordinal)
}
}
}
}
Then it should take the one stored in Data Store
instead of using the system one.
Here's a full example Compose theme switcher from Stefano Sansone
Also you could take a read on Jetpack Compose Theming
CodePudding user response:
Found a solution, correct me if something is wrong.
Inherit our Activity from AppCompatActivity instead of ComponentActivity
Use AppDelegateCompat.setDefaultNightMode() tell the application which theme we are going to use
Call delegate.applyDayNight()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Getting user's saved selection appTheme.value = runBlocking(Dispatchers.IO) { getSavedAppTheme(context) } // Telling the application which mode to use when (appTheme.value) { AppTheme.SYSTEM_THEME -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppTheme.NIGHT_THEME -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) AppTheme.LIGHT_THEME -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } delegate.applyDayNight() setContent { MyAppTheme { MyAppComposable() } } }
Handling user selection:
suspend fun setAppTheme(context: Context, theme: AppTheme) {
// Saving selection to cache
saveAppTheme(context, theme)
// Applying selection
appTheme.value = theme
// Telling the application which mode to use
when (appTheme.value) {
AppTheme.SYSTEM_THEME -> setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM)
AppTheme.NIGHT_THEME -> setDefaultNightMode(MODE_NIGHT_YES)
AppTheme.LIGHT_THEME -> setDefaultNightMode(MODE_NIGHT_NO)
}
}
UPD:
Point 2 is better to call in Application.onCreate() so as not to reload Activity inside onCreate()