Your first composable
A composable is an ordinary Kotlin function wearing one extra label. Write one, call it from your Activity, and it appears on the phone. Then give it parameters and it becomes a piece you can reuse anywhere.
The smallest unit of a screen
In Compose there is no "screen object" and no "view object". There is a function.
You already know how to write functions — you spent Part 1 doing it. A is that same thing with one extra label stuck on the front, and one different job: instead of returning a value, it describes a piece of screen.
Here is a complete, real one:
1@Composable
2fun Greeting() {
3 Text(text = "Hello from Compose")
4}That is not a simplified teaching example. That is a genuine composable, and you could ship it.
Think of a rubber stamp.
A stamp is carved once. After that you can press it onto a hundred pages and get the same shape every time. If you carve a stamp with a blank box on it, you can write something different in the box on each page — same stamp, different result.
A composable is a stamp for a bit of screen. @Composable is the handle that says "this is a stamp, not a paragraph of ordinary instructions". are the blank boxes.
And exactly like a stamp, it has no memory of the pages it has already printed. That single fact explains almost everything about how Compose behaves — including a puzzle in Lesson 3.5.
What @Composable actually is
@Composable is an — a label attached to your code that some tool reads. This one is read by the Compose compiler, a plugin that runs as part of your build. Your project turns it on with a single line in the build file:
1plugins {
2 alias(libs.plugins.android.application)
3 alias(libs.plugins.kotlin.android)
4 alias(libs.plugins.kotlin.compose)
5}That third line is the whole setup. When the Compose compiler sees @Composable, it rewrites your function behind the scenes so it can be tracked, skipped and re-run intelligently. You never see the rewritten version, and you never need to.
The label buys the function two powers:
- It may call other composables.
Text,Column,Button— all composables. Only a composable can call one. - It may read state and be re-run automatically when that state changes. That is Lesson 3.5 and 3.6.
And it comes with one restriction, which follows from those powers: a composable can only be called from another composable. Try it from an ordinary function and the compiler stops you immediately. That error is in the Error Doctor below, and you will meet it for real.
Three rules for writing one
- Name it like a thing, not an action.
PlayerPanel, notdrawPlayerPanel. Composables are named inPascalCasewith a capital first letter, unlike every other Kotlin function you have written. This is deliberate: a capital tells you at a glance that a call puts something on screen. - Return nothing. A composable's type is
Unit, which is Kotlin's way of saying "no useful value". It does not hand you a screen. It emits one, as a side effect of running. - Take parameters for everything that varies. That is what turns one function into a piece you use in ten places.
Making it appear
A composable does nothing on its own. Something has to call it, and the first call comes from your :
1package com.yourname.composelab
2
3import android.os.Bundle
4import androidx.activity.ComponentActivity
5import androidx.activity.compose.setContent
6import androidx.compose.material3.Text
7import androidx.compose.runtime.Composable
8
9class MainActivity : ComponentActivity() {
10 override fun onCreate(state: Bundle?) {
11 super.onCreate(state)
12 setContent {
13 Greeting()
14 }
15 }
16}
17
18@Composable
19fun Greeting() {
20 Text(text = "Hello from Compose")
21}Press Run and there it is: black text in the top-left corner of a white screen, with no padding and no styling, because you have not asked for any. Compose gives you exactly what you described and nothing else.
Greeting() on its own. No padding, because none was asked for.
Parameters turn one stamp into many
A Greeting that always says the same thing is not much use. Add a and the same function serves every case:
1@Composable
2fun PlayerLine(name: String, score: Int) {
3 Text(text = "$name: $score")
4}Now call it twice with different :
1@Composable
2fun DuelScreen() {
3 Column(modifier = Modifier.padding(24.dp)) {
4 Text(text = "DICE DUEL")
5 PlayerLine(name = "Player 1", score = 12)
6 PlayerLine(name = "Player 2", score = 9)
7 }
8}DuelScreen(). Three lines of text in a 24dp-padded column.
That is the whole model. Your composables call other composables, which call other composables, until eventually everything bottoms out in the handful that Compose knows how to actually draw. The result is a tree, and the tree is your screen.
Seeing it without running the app
Building and installing an takes a few seconds even on a fast phone. When you are nudging a number back and forth, that adds up. So Compose has previews:
1@Preview(showBackground = true)
2@Composable
3private fun DuelScreenPreview() {
4 DuelScreen()
5}Two labels: @Preview says "render this without launching the app", and @Composable because it is still a composable. Mark it private — nothing else should ever call it.
A preview function must take no parameters, which is why you write a tiny wrapper that supplies fixed values rather than previewing PlayerLine directly. Notice how easy that is here: because PlayerLine takes its data as parameters, you can preview it with any values you like, including ones that would be awkward to reach in the real app — a score of 999, an empty name, a player called Bartholomew-Fitzgerald. Designing for the preview quietly makes your code better.
Previews are a tooling feature, and how they appear varies between versions of Pocket Studio. If you do not see a preview pane, nothing is broken — press Run instead. Every code sample in this course works either way.
Make a fresh project to play in for the whole of Part 3.
- Open Pocket Studio and tap Projects, then New Project.
- Choose the Empty Compose Activity template.
- Name it
ComposeLab. Leave the package as the suggested one, or set it tocom.yourname.composelab. - Tap Create. Wait for the first — it is the slow one, as Lesson 2.6 explained.
- Open
app/src/main/java/…/MainActivity.ktfrom the file tree. - Delete the generated
Greetingfunction and whatever the template put insidesetContent. - Type the
DuelScreenandPlayerLinefunctions from this lesson, and putDuelScreen()insidesetContent { }. - Tap the import prompt when Pocket Studio offers it, or add the imports by hand:
androidx.compose.material3.Text,androidx.compose.runtime.Composable,androidx.compose.foundation.layout.Column,androidx.compose.foundation.layout.padding,androidx.compose.ui.Modifier,androidx.compose.ui.unit.dp. - Tap Run. Three lines of text should appear.
- Change
score = 12toscore = 30, Run again, and watch it follow.
Text, Column, your own one — from a function that is not itself marked @Composable. This is the single most common Compose error there is.@Composable on the line above the calling function. If the call is inside a lambda such as onClick = { ... }, that lambda is not composable, and you need a different approach — see Lesson 3.8.import androidx.compose.runtime.Composable at the top of the file.@Preview comes from the tooling library, which is a separate import from everything else.import androidx.compose.ui.tooling.preview.Preview, and check app/build.gradle.kts lists implementation(libs.androidx.ui.tooling.preview).fun Greeting(): String — and then called Text inside it. Text produces nothing to return, so the function's promise is broken.Unit: @Composable fun Greeting() { ... }. If you truly need a value, compute it in an ordinary function and pass the result in as a parameter.Text — usually a number, as in Text(score).Text(text = "$score"). Compose will not quietly convert an Int to a String for you, which is a good thing — it means "Score: " + 3 never turns into something you did not intend.- A is a normal Kotlin marked
@Composable. It describes a piece of screen instead of returning a value. - Name them in
PascalCase, like nouns:PlayerLine,DuelScreen. - Only a composable can call a composable. Everything starts from
setContent { }in your . - are what make one composable reusable in many places — and previewable with any values you like.
@Previewrenders a composable without launching the app. The preview function takes no parameters, so wrap the real one.- Next: — how you add padding, size, background and taps to anything on screen, and why the order of the chain changes the result.