Resources: strings, colours, drawables
Everything in your app that is not code lives in the res folder. Learn why text belongs there rather than in your Kotlin, how folder name suffixes let Android swap resources for dark mode or another language, and what the R class actually is.
The folder that makes translation possible
Here are two ways to put a title on a screen.
1// The obvious way.
2Text("Dice Duel")
3
4// The Android way.
5Text(stringResource(R.string.app_name))The first is shorter and reads better. The second is what every real app does, and it is worth understanding why before you decide the first one is fine.
The moment your app has a second language, a dark mode, or a tablet layout, the first version has to be found and changed by hand in every file it appears in. The second version does not change at all — you add one file, and Android picks it.
Think about a theatre that runs the same play in two versions.
The script says "the drawing room". It does not say "the drawing room with the blue wallpaper". Backstage there are two complete sets built for that scene: a bright daytime one and a gloomy evening one. When the curtain goes up, the stage manager wheels out whichever the performance calls for.
The script never changes. Only the set does.
work exactly like that. Your code names the thing it wants. Android looks at the phone right now — dark mode? French? big screen? — and wheels out the matching version.
What lives in res
Here is the complete res folder from Dice Duel. It is small on purpose.
1app/src/main/res/
2 drawable/ic_launcher_foreground.xml
3 mipmap-anydpi-v26/ic_launcher.xml
4 mipmap-anydpi-v26/ic_launcher_round.xml
5 values/ic_launcher_background.xml
6 values/strings.xml
7 values/themes.xml
8 values-night/themes.xmlThree kinds of thing are in there.
values/— anything that is really just a name and a value: strings, colours, dimensions, styles. Several per file.drawable/— pictures. One per file.mipmap/— launcher icons only. It is a separate folder purely so that the icon survives when unused screen densities are stripped out of a shipped app.
Strings
The simplest resource file in Android:
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <string name="app_name">Dice Duel</string>
4 <string name="roll">Roll</string>
5 <string name="player_one">Player 1</string>
6</resources>Two ways to reach those, depending on where you are:
| From | Written as |
|---|---|
| XML (manifest, themes) | @string/app_name |
| Compose | stringResource(R.string.app_name) |
| Plain Kotlin in an Activity | getString(R.string.app_name) |
The @ in XML means "look this up". The R. in Kotlin means the same thing.
What R actually is
R is not something Google wrote. The build generates it, fresh, from your own files, every time you build.
reads every file under res, gives each resource a number, and writes a Kotlin- and Java-visible class listing them:
1// Roughly what the build writes for you.
2object R {
3 object string {
4 const val app_name = 0x7f0e001c
5 const val roll = 0x7f0e0021
6 }
7}Two useful consequences of knowing that.
First, R.string.app_name is checked by the . Misspell it and the build fails immediately, instead of the app crashing in front of a user. A raw "Dice Duel" string has no such protection.
Second, when you see Unresolved reference: R in a file that was fine yesterday, it almost always means the could not be generated — because some other resource file has an error in it. The real problem is somewhere in res, not in the file the editor is shouting about.
Qualifiers — the actual magic
This is the part that pays for all the indirection.
Add a suffix to a folder name and Android will use that folder only in the matching situation. Same resource names inside; different values.
| Folder | Used when |
|---|---|
values/ | Always, unless something better matches |
values-night/ | The phone is in dark mode |
values-fr/ | The phone's language is French |
values-sw600dp/ | The screen is at least 600dp wide — a tablet |
drawable-hdpi/ | The screen is roughly 1.5× density |
layout-land/ | The phone is in landscape |
Dice Duel uses exactly one — -night:
1<style
2 name="Theme.DiceDuel"
3 parent="android:Theme.Material.NoActionBar">
4 <item name="android:windowBackground">#FF121022</item>
5</style>Same style name, darker colour, different parent. Your code never mentions night mode anywhere. Android picks the file.
And now Lesson 2.2 clicks into place: this is why rotation destroys your Activity. Changing the phone's language, orientation or theme changes which folder wins. Rebuilding the screen is how Android re-runs the picking.
A theme, line by line
themes.xml is where a lot of beginners get lost, because in a Compose app it does much less than it looks like it should.
Honest warning about a genuine confusion. In a Compose app your app colours, fonts and shapes live in Kotlin, in ui/theme/Theme.kt. This XML theme only styles the window around Compose. Two files with "theme" in the name, doing different jobs. Lesson 3.11 builds the Kotlin one.
Drawables
A is anything Android can draw. It can be a PNG, but on Android it is far more often XML describing shapes:
1<vector
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:width="24dp"
4 android:height="24dp"
5 android:viewportWidth="24"
6 android:viewportHeight="24">
7 <path
8 android:fillColor="#FFFFFF"
9 android:pathData="M12 2 L22 22 L2 22 Z" />
10</vector>That is a : a triangle described as instructions rather than pixels. It is about 250 bytes, it is razor sharp at any size, and it needs no separate copy per screen density. For icons, always prefer this to a PNG. Lesson 6.1 draws a real app icon this way.
The naming rules
Resource file names are strict, and the error message when you break the rule is not friendly.
- Lowercase letters, digits and underscores only.
- Must start with a letter.
- No spaces, no dashes, no capitals.
dice_face_six.xml is fine. Dice Face 6.xml gives you three separate errors at once.
- Open Pocket Studio, tap Projects, open your Part 0 app.
- Tap Editor, open the file tree, and open
app/src/main/res/values/strings.xml. - Add a second string on its own line inside
<resources>:<string name="tagline">Built on a phone</string> - Open your main Compose file and show it. Add
Text(stringResource(R.string.tagline))next to your existing text, and add the importandroidx.compose.ui.res.stringResourceif the editor asks. - Tap Build, press Run. The new text appears.
- Now make it lie about its own name. Change the reference to
R.string.taglinand press Run again. Read the error — the build stops before the app is ever built. - Put it back, then create the folder
app/src/main/res/values-night/and inside it a filestrings.xmlcontaining the same<resources>wrapper and<string name="tagline">Built on a phone, at night</string>. - Press Run, then switch your phone to dark mode from the quick settings. The text changes. You did not write a single
if.
res. Usually a typo, sometimes a file you renamed or deleted.res/values/strings.xml. Names are case-sensitive, and the error text shows exactly what was asked for.AAPT: error: line a few lines above, and it names the file and resource.res has a capital letter, a space or a dash in its name. Android's resource names become Kotlin identifiers, so they follow Kotlin's rules.Dice Face.png becomes dice_face.png — then update anywhere that referred to the old name.strings.xml twice, or exists in two different files under values/.res/values folder for the name and delete one of the two. The second error line points at the first definition.AAPT: error: higher up the build output. Fix that, and R reappears. If there is genuinely no other error, check that the import at the top of your file matches your package name.- Everything that is not code lives in
res: invalues/, indrawable/, launcher icons in . - Refer to them with
@string/namefrom XML andR.string.namefrom Kotlin. The is generated from your own files on every build. - like
-night,-frand-sw600dplet Android pick a different version with noifin your code — which is exactly why a rebuilds a screen. - beat PNGs for icons: tiny, sharp at every size, one file.
- Resource names are lowercase, digits and underscores only.
- Next: the free lesson everyone asks for — what Gradle actually is, in plain English.