Mobile development: Android

Sefism.

Chapter 02

UI and layout

Turning a design into screens: small composable pieces, layout containers, long lists, forms, theming and fitting every screen size.

Declarative UI

What
You describe what the screen should look like for the current state, and the framework works out what to change on screen. You never update a label by hand.
Why
Flutter, React Native, Jetpack Compose and SwiftUI all work this way. It removes whole classes of bugs where the screen and the data disagree.
Kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
  Text(text = "Hello, $name", modifier = modifier)
}

Watch out

Doing slow work, like network calls or sorting big lists, inside the build or render function, which can run many times a second.

Modifiers

What
A Modifier is an ordered chain that decorates a composable with size, padding, background, click handling, semantics and more.
Why
Most layout and styling is done with modifiers, and accepting a modifier parameter makes your own composables reusable.
Kotlin
Text(
  "Hello",
  modifier = Modifier
    .padding(16.dp)
    .background(Color.Yellow)
    .clickable { onTap() },
)

Watch out

Order matters: here padding comes first, so neither the background nor the click area covers the padding.

Recomposition and side effects

Also called remember, LaunchedEffect, recomposition, side effect

What
When state a composable reads changes, Compose runs that composable again (recomposition). remember keeps a value across recompositions, and effect APIs like LaunchedEffect run work tied to a key.
Why
Composables can run often and in any order, so work such as loading data must go in an effect, not the function body.
Kotlin
@Composable
fun Profile(userId: String, vm: ProfileViewModel) {
  LaunchedEffect(userId) {
    vm.load(userId)
  }
  val state by vm.state.collectAsStateWithLifecycle()
  ProfileContent(state)
}

Watch out

Starting a network call directly in a composable body fires it on every recomposition.

Rows, columns and stacks

Also called Row, Column, HStack, VStack

What
The basic layout containers place children horizontally, vertically or on top of each other, with spacing and alignment options.
Why
Almost every screen is nested rows and columns. Splitting a design into these boxes is the first step of building any screen.
Kotlin
Row(
  verticalAlignment = Alignment.CenterVertically,
  horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
  Icon(Icons.Default.Person, contentDescription = null)
  Text(user.name, modifier = Modifier.weight(1f))
}

Lazy lists

Also called ListView.builder, FlatList, LazyColumn, virtualised list

What
A lazy list only builds the rows currently on screen, plus a few ahead, and recycles them as you scroll. Each row gets a stable key.
Why
Building thousands of rows at once uses lots of memory and makes scrolling stutter. Stable keys keep row state and animations attached to the right item.
Kotlin
LazyColumn {
  items(posts, key = { it.id }) { post ->
    ListItem(headlineContent = { Text(post.title) })
  }
}

Watch out

Using the array index as the key, so rows show the wrong state after an insert, delete or reorder.

Text input and forms

What
A text field shows a value from state and reports every change back. Keyboard type, autofill hints and validation messages make forms fast to fill in.
Why
Login, sign-up and checkout are forms, and small details like the email keyboard and autofill decide whether people finish them.
Kotlin
var email by rememberSaveable { mutableStateOf("") }

OutlinedTextField(
  value = email,
  onValueChange = { email = it },
  label = { Text("Email") },
  keyboardOptions = KeyboardOptions(
    keyboardType = KeyboardType.Email,
  ),
  singleLine = true,
)

Watch out

Forgetting that the keyboard covers the bottom of the screen, hiding the field or the submit button.

Theming and dark mode

Also called dark mode, semantic colour, dynamic colour

What
A theme defines colours, typography and shapes in one place. Components read colours from the theme by role, such as primary or surface, and the app switches palettes when the system switches to dark mode.
Why
A theme keeps the app consistent and makes dark mode and brand changes a one-file job instead of a hunt through every screen.
Kotlin
@Composable
fun AppTheme(content: @Composable () -> Unit) {
  val dark = isSystemInDarkTheme()
  val context = LocalContext.current
  val colors = if (Build.VERSION.SDK_INT >= 31) {
    if (dark) dynamicDarkColorScheme(context)
    else dynamicLightColorScheme(context)
  } else {
    if (dark) darkColorScheme() else lightColorScheme()
  }
  MaterialTheme(colorScheme = colors, content = content)
}

Watch out

Hard-coding colours like white text or black backgrounds, which become invisible in the other mode.

Platform design guidelines

Also called Material Design, Human Interface Guidelines, HIG, Material 3, Cupertino

What
Material Design 3 is Google's design system for Android, and the Human Interface Guidelines are Apple's rules for iOS. They cover navigation patterns, spacing, typography, controls and touch target sizes.
Why
Users expect apps to behave like the rest of their phone. Following the guidelines avoids confusing navigation and some App Store review problems.
Example
Minimum touch targets are 48 by 48 dp on Android and 44 by 44 points on iOS. Android users expect the system back gesture to work everywhere; iOS users expect to swipe from the left edge to go back.

Safe areas and screen sizes

Also called edge-to-edge, insets, responsive layout, safe area, edge to edge, system bars, window size class, Dynamic Island

What
Safe areas, or insets, are the parts of the screen not covered by the notch, status bar, camera cutout or gesture bar. Responsive layouts adapt to small phones, large phones, foldables and tablets.
Why
Content under the status bar or home indicator is unreadable or untappable. Android 15 draws apps edge to edge by default, so insets must be handled on purpose.
Example
Flutter uses SafeArea and LayoutBuilder. React Native uses react-native-safe-area-context and useWindowDimensions. Compose uses WindowInsets and window size classes. SwiftUI respects safe areas by default and uses size classes.

Watch out

Testing only on one large emulator; always check the smallest supported phone and landscape.

Hot reload and previews

Also called Fast Refresh, Compose Preview, Xcode Previews, hot reload, hot restart, Live Edit

What
Tools that show code changes in a running app or an editor preview within a second or two, without rebuilding the whole app.
Why
Layout work is trial and error, and a fast loop makes it quick. Flutter has hot reload, React Native has Fast Refresh, Compose has @Preview and Live Edit, and SwiftUI has #Preview.

Watch out

Hot reload keeps existing state, so changes to initial state or main need a full restart to show.

02 · UI and layout2 / 8