🎨 UI – Views & Layouts · Lesson 19
Material 3 Theme, Colors & Dark Mode
Theme vs style
Theme
- Applied to the whole app or an Activity
- Defines colour roles, fonts, shapes
- android:theme="@style/Theme.StudentDiary"
Style
- Applied to one view
- Groups attributes (size, padding)
- style="@style/StatNumber"
Material 3 colour roles
| Role | Used for |
|---|---|
| colorPrimary / colorOnPrimary | Main buttons, FAB, active tab · text on them |
| colorPrimaryContainer / colorOnPrimaryContainer | Tonal buttons, chips, highlighted cards |
| colorSecondary / colorTertiary | Accents |
| colorSurface / colorOnSurface | Screen and card background · normal text |
| colorError | Error text and icons |
The app theme
XML – res/values/colors.xml (Student Diary)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="brand">#16A34A</color>
<color name="brand_dark">#15803D</color>
<color name="brand_light">#DCFCE7</color>
<color name="accent">#F59E0B</color>
<color name="star">#F59E0B</color>
<color name="priority_high">#EF4444</color>
<color name="priority_medium">#F59E0B</color>
<color name="priority_low">#22C55E</color>
</resources>XML – res/values/themes.xml (Student Diary)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Material 3, light + dark (DayNight). We use our own Toolbar, so NoActionBar. -->
<style name="Theme.StudentDiary" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/brand</item>
<item name="colorOnPrimary">@android:color/white</item>
<item name="colorPrimaryContainer">@color/brand_light</item>
<item name="colorOnPrimaryContainer">@color/brand_dark</item>
<item name="colorSecondary">@color/accent</item>
<item name="colorOnSecondary">@android:color/black</item>
</style>
<style name="StatCard" parent="Widget.Material3.CardView.Filled">
<item name="cardCornerRadius">16dp</item>
</style>
<style name="StatBox">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:orientation">vertical</item>
<item name="android:gravity">center</item>
<item name="android:padding">16dp</item>
</style>
<style name="StatNumber">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">28sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?attr/colorPrimary</item>
<item name="android:text">0</item>
</style>
<style name="StatLabel">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textAppearance">?attr/textAppearanceLabelLarge</item>
</style>
</resources>Dark mode with values-night
A file with the same name in values-night/ overrides the theme when dark mode is on.
XML – res/values-night/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Dark mode: lighter green looks better on a dark background -->
<style name="Theme.StudentDiary" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">#4ADE80</item>
<item name="colorOnPrimary">#052E16</item>
<item name="colorPrimaryContainer">#14532D</item>
<item name="colorOnPrimaryContainer">#DCFCE7</item>
<item name="colorSecondary">@color/accent</item>
<item name="colorOnSecondary">@android:color/black</item>
</style>
</resources>Use theme attributes, not fixed colours
XML
<!-- ❌ fixed colour – stays white in dark mode --> <LinearLayout android:background="#FFFFFF" /> <!-- ✅ theme attributes – change automatically --> <LinearLayout android:background="?attr/colorSurface" /> <TextView android:textColor="?attr/colorOnSurface" /> <TextView android:textColor="?attr/colorPrimary" /> <TextView android:textAppearance="?attr/textAppearanceTitleMedium" />
Reusable styles
The StatCard, StatBox, StatNumber and StatLabel styles in the theme file above are used three times on the Progress screen — change once, update everywhere.
A dark mode switch
Kotlin – data/SettingsStore.kt
package com.codingeasily.studentdiary.data
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
/** Small key-value settings saved with SharedPreferences. */
class SettingsStore(context: Context) {
private val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var userName: String
get() = prefs.getString(KEY_NAME, "Student") ?: "Student"
set(value) = prefs.edit { putString(KEY_NAME, value) }
var darkMode: Boolean
get() = prefs.getBoolean(KEY_DARK, false)
set(value) = prefs.edit { putBoolean(KEY_DARK, value) }
var remindersEnabled: Boolean
get() = prefs.getBoolean(KEY_REMINDERS, true)
set(value) = prefs.edit { putBoolean(KEY_REMINDERS, value) }
fun applyTheme() {
AppCompatDelegate.setDefaultNightMode(
if (darkMode) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO
)
}
companion object {
private const val KEY_NAME = "user_name"
private const val KEY_DARK = "dark_mode"
private const val KEY_REMINDERS = "reminders"
}
}Kotlin – using it
// From Student Diary – SettingsFragment
binding.switchDark.isChecked = settings.darkMode
binding.switchDark.setOnCheckedChangeListener { _, checked ->
settings.darkMode = checked
settings.applyTheme() // the activity is recreated with the new theme
}
// StudentDiaryApp.onCreate() – apply the saved choice when the app starts
SettingsStore(this).applyTheme()Dynamic colour (Android 12+)
Kotlin
// app module: Application class
class StudentDiaryApp : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this) // wallpaper colours on Android 12+
}
}Optional: the app picks colours from the user's wallpaper on Android 12+, and uses your colours on older phones.
Common mistakes
- Hard-coded
#FFFFFFbackgrounds and#000000text → unreadable in dark mode. - Parent theme from MaterialComponents (M2) mixed with Material3 widget styles → wrong shapes/colours.
- Forgetting the night version of a custom colour → same bright colour in dark mode.
Summary
- Theme = whole app (colour roles); style = one view.
Theme.Material3.DayNight.NoActionBar+values-night/= automatic dark mode.- Use
?attr/colorSurface,?attr/colorPrimaryetc. in layouts. - Save a user choice and call
AppCompatDelegate.setDefaultNightMode.
✅ Practice
Generate a scheme in Material Theme Builder with your favourite colour, paste it into your app, and add a three-option dialog: Light / Dark / System.
Frequently Asked Questions
How do I generate a full Material 3 colour scheme?
Use the Material Theme Builder (m3.material.io/theme-builder): pick a seed colour and export Android XML. It creates all colour roles for light and dark themes.
Why does my activity restart when I change dark mode?
AppCompatDelegate.setDefaultNightMode recreates activities so the new resources load. Keep data in a ViewModel so nothing is lost.
Can I follow the phone's dark mode setting?
Yes. Use MODE_NIGHT_FOLLOW_SYSTEM. It is the default when you never call setDefaultNightMode.
