Theming & Dark Mode
Theming is what makes every screen of TrailLog feel like it belongs to the same app — consistent
colors, consistent text sizes, consistent default look for every button and card, all defined in
one place instead of repeated (and inevitably drifting out of sync) across dozens of layout files.
This lesson covers how themes.xml and colors.xml work together, and how
to support both light and dark system appearance correctly.
themes.xml: The Central Definition of Your App's Look
res/values/themes.xml is where an app's overall style lives — its primary and accent
colors, default text appearance, and the default look Material components fall back to when a
layout doesn't override them explicitly. Material
Components covered why every Material widget requires a Material-derived theme to even
render correctly; this lesson is about what actually goes inside that theme.
res/values/themes.xml
<style name="Theme.TrailLog" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/trail_green</item>
<item name="colorOnPrimary">@color/trail_on_primary</item>
<item name="android:windowBackground">@color/trail_background</item>
</style>
Notice every value here is a @color/ reference, never a hardcoded hex value — that's
deliberate, and it's the next section's whole point.
Colors Belong in colors.xml, Defined Once
Every color TrailLog uses should be declared exactly once in res/values/colors.xml
and referenced everywhere else by name — @color/trail_green — rather than typing
#4CAF50 directly into a dozen different layout files. The reason this matters is
purely practical: the day the brand's green needs to shift slightly, editing one line in
colors.xml updates every screen at once. Hardcoded hex values scattered across
layouts mean hunting through the entire project, and near-certainly missing one.
res/values/colors.xml (light mode)
<resources>
<color name="trail_green">#2E7D32</color>
<color name="trail_on_primary">#FFFFFF</color>
<color name="trail_background">#FAFAFA</color>
<color name="trail_text_primary">#1B1B1B</color>
</resources>
Automatic Day/Night Support with values-night
Android's resource system already has a mechanism built for exactly this: create a second
colors.xml under a res/values-night/ folder, using the same color
names but dark-appropriate values. Android automatically picks whichever file matches the
device's current system setting — no conditional code required anywhere in TrailLog itself.
res/values-night/colors.xml (dark mode)
<resources>
<color name="trail_green">#66BB6A</color>
<color name="trail_on_primary">#0A0A0A</color>
<color name="trail_background">#121212</color>
<color name="trail_text_primary">#ECECEC</color>
</resources>
Because every layout and theme references @color/trail_background rather than a raw
value, this is the entire dark mode implementation for those colors — no Kotlin code needed, and
no risk of one screen forgetting to support dark mode while others do.
Letting the User Override the System Setting
Following the system setting is the right default, but many apps also offer their own Light/Dark/
System Default toggle inside Settings. AppCompatDelegate.setDefaultNightMode() is
the call that applies that override at runtime:
A settings screen toggle for TrailLog's theme
fun applyThemeChoice(choice: ThemeChoice) {
val mode = when (choice) {
ThemeChoice.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
ThemeChoice.DARK -> AppCompatDelegate.MODE_NIGHT_YES
ThemeChoice.SYSTEM_DEFAULT -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
AppCompatDelegate.setDefaultNightMode(mode)
}
enum class ThemeChoice { LIGHT, DARK, SYSTEM_DEFAULT }
Calling this immediately re-themes every open Activity in the app. The one piece it doesn't
handle is remembering the user's choice the next time TrailLog launches — for that you'd persist
the selected ThemeChoice using Jetpack DataStore, which is exactly what the next
lesson, DataStore Preferences, is about.
Previewing Dark Mode Without Running the App
Android Studio's layout preview and the emulator both let you flip between light and dark rendering without changing any code — a small sun/moon toggle above the preview pane switches the configuration, and the emulator's own quick settings include a dark theme switch. Checking both variants for every new screen you add is a cheap habit that catches low-contrast text or an invisible icon well before a user ever reports it.