This episode masters Compose's basic layout blocks: Column, Row, Box, LazyColumn, and LazyRow, Material 3 components like Button, TextField, and Card, the Modifier system for spacing and alignment, and responsive layout strategies.

Your project is up and your first composable is running. Now it's time to build real screens. Episode 4 equips you with Compose's layout building blocks: Column, Row, Box, and the Lazy collections, plus Material 3 components.
The key to fluency here is understanding Modifier. Almost every visual aspect — spacing, size, position, clicks — is handled through modifiers, not through XML attributes like before. Once you master Modifier, reading other people's layouts becomes easy.
Episode 4 covers core layout composables, Material components, the Modifier system, and responsive layout.
These three basic layouts make up 90 percent of your screens: Column stacks children vertically, Row arranges them horizontally, and Box stacks elements on top of one another.
@Composable
fun ProfilSingkat() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Arman Dwi", style = MaterialTheme.typography.titleLarge)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(onClick = { }) { Text("Ikuti") }
OutlinedButton(onClick = { }) { Text("Pesan") }
}
Box(
modifier = Modifier
.size(64.dp)
.background(MaterialTheme.colorScheme.primary)
) {
Text("logo", Modifier.align(Alignment.Center))
}
}
}Arrangement.spacedBy(8.dp) gives consistent spacing between children, and Alignment positions them. Inside Box, Modifier.align(Alignment.Center) centers the text in the box — a common pattern for badges and overlays.
For long lists, use LazyColumn (vertical) and LazyRow (horizontal). Both only render the items that are visible — an efficiency a regular Column doesn't have. The details of lazy lists are covered in depth in episode 9.
LazyColumn {
items(20) { index ->
ListItem(
headlineContent = { Text("Item ke-$index") }
)
}
}items(20) creates items lazily based on the index. As your list grows larger, you'll use items with data lists and key (episodes 9 and 15).
Material 3 provides variants of Button, Text, and text input that directly use your theme:
Button(onClick = { onKirim() }) {
Text("Kirim")
}
OutlinedTextField(
value = pesan,
onValueChange = { pesan = it },
label = { Text("Pesan") },
modifier = Modifier.fillMaxWidth()
)OutlinedTextField is the standard Material 3 text input with a built-in label. onValueChange sends changes for every character — a state pattern that will be unpacked in episodes 5 and 7.
Card wraps content in a raised surface, while Scaffold provides a screen skeleton with a top bar, content, and a floating action button:
Scaffold(
topBar = { TopAppBar(title = { Text("Beranda") }) },
floatingActionButton = {
FloatingActionButton(onClick = { }) {
Text("+")
}
}
) { padding ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(padding)
.padding(16.dp)
) {
Text("Konten utama", Modifier.padding(16.dp))
}
}Scaffold passes padding through its content lambda — you must apply this padding to the content so it isn't hidden behind the top bar. The slot API pattern you saw in episode 2 is at play here.
Modifiers are chained with dot notation. Order matters: the first modifier is the outermost one. The example below sets size, then padding, then background — the final background follows the area after padding:
Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.LightGray)
.clip(RoundedCornerShape(8.dp))clip constrains the shape of the following content, so placing it after background clips the corners. Modifiers like .weight inside a Row or Column make elements fill the remaining space — the basis of responsive layout.
Android screen sizes vary. BoxWithConstraints gives you the available constraints so the layout can adapt:
@Composable
fun DaftarBerita() {
BoxWithConstraints {
val isLebar = maxWidth > 600.dp
if (isLebar) {
Row {
HeaderBerita(Modifier.weight(1f))
DetailBerita(Modifier.weight(1f))
}
} else {
Column {
HeaderBerita(Modifier.fillMaxWidth())
DetailBerita(Modifier.fillMaxWidth())
}
}
}
}maxWidth > 600.dp detects wide screens (tablets or landscape), then switches from a Column to a Row with weight. This is a simple pattern to start building adaptive UI. To find out the screen size of the active emulator, run adb shell wm size and adb shell wm density in the terminal.
Episode 4 equipped you with layout building blocks: Column, Row, Box for basic arrangement, LazyColumn and LazyRow for lists, Material 3 components like Button, TextField, Card, and Scaffold, the Modifier system for visual control, and BoxWithConstraints for responsive layout.
Key takeaways:
weight inside Row and Column makes elements fill space.In episode 5 we will discuss state and recomposition — state hoisting, remember, mutableStateOf, ViewModel integration, snapshotFlow and derivedStateOf, and strategies for managing state so the UI always stays in sync with data.