Pocket Studio Academy
HomePart 55.8

Pocket Notes 3 — the note list and the empty state

Full course15 min read·4 questions

Turn a plain column of text into a real list: cards, human-readable timestamps, a title that shrinks as you scroll, and the screen everybody forgets to design — the one with nothing on it.

The screen you see most, and the screen you see first

Two screens matter more than any other in a notes app.

The list is the one you look at hundreds of times. It has to survive four hundred notes without getting slow, and it has to be readable at a glance, which means a preview and a timestamp that says "3 h ago" rather than 1755262800000.

The empty screen is the one you see once — on the very first launch, before you have written anything. It is the app's first impression, and in most apps it is a blank white void that makes you wonder whether something crashed.

This chapter builds both, and by the end the app finally looks like an app.

Think of it like this

Think about a roll of paper towels.

The plain Column you wrote in chapter 2 is what happens if you unroll the whole thing across the kitchen floor to find one sheet. It works. With four sheets it is instant. With four hundred you have a paper towel problem.

A keeps the roll rolled up. Only the few sheets actually passing your hand are unrolled, and the ones you scroll past get rolled away again behind you.

The list can be a thousand notes long. Compose still only ever builds the dozen you can see.

One new dependency

The empty state needs a pencil-on-paper icon, and the list needs a plus. The handful of icons bundled with Material 3 does not include the first one, so this chapter adds the full set.

app/build.gradle.ktskts
1    implementation(libs.androidx.material3)
2    implementation(
3        libs.androidx.material.icons.extended
4    )

That artifact is large — a few thousand vector icons — but only the ones you actually reference end up in the , because strips the rest out of a release build.

Milliseconds are for computers

updatedAt is a Long. Nobody wants to read a Long.

That java.time package is the reason this app sets minSdk = 26. It arrived with Android 8.0, and on anything older the build fails outright — the last entry in the Error Doctor below.

A note, drawn as a card

The screen with nothing on it

A blank screen is a screen you shipped without thinking about. An is a screen you designed.

The rules are the same every time: a symbol so the eye has somewhere to land, a headline that is not an apology, and one sentence saying what to do next.

ui/EmptyNotes.ktkotlin
1@Composable
2fun EmptyNotes(modifier: Modifier = Modifier) {
3    EmptyMessage(
4        icon = Icons.Outlined.EditNote,
5        title = "No notes yet",
6        body = "Tap the + button to write your first " +
7            "one. Everything you type is saved on " +
8            "this phone, and only on this phone.",
9        modifier = modifier
10    )
11}

EmptyNotes is a thin wrapper around a private composable called EmptyMessage, which takes an icon, a headline and a sentence. That split looks like over-engineering for one empty state. It is not: chapter 6 adds a second one for "your search found nothing", and it will be six lines because of this decision.

The layout underneath is a centred Column with three things in it:

ui/EmptyNotes.ktkotlin
1        Surface(
2            shape = CircleShape,
3            color = colors.primaryContainer,
4            modifier = Modifier.size(96.dp)
5        ) {
6            Box(
7                contentAlignment = Alignment.Center
8            ) {
9                Icon(
10                    imageVector = icon,
11                    contentDescription = null,
12                    tint = colors.onPrimaryContainer,
13                    modifier = Modifier.size(44.dp)
14                )
15            }
16        }

A 96dp Surface clipped to a circle, filled with primaryContainer — the soft honey #FFDDB8 — with a 44dp icon centred inside it. contentDescription = null is correct here and not laziness: the icon says nothing the headline underneath does not already say, so announcing it twice to a screen reader is noise.

At the very bottom of that Column sits one more line:

ui/EmptyNotes.ktkotlin
1        // Nudges the block above dead centre, which
2        // reads better than true centre.
3        Spacer(Modifier.height(64.dp))

A 64dp spacer after everything, inside a vertically centred column, pushes the whole block up by 32dp. Mathematically centred and optically centred are not the same thing, and every designer you will ever work with knows it.

The list screen, rebuilt

That nested if is the payoff for putting loading in NotesUiState back in chapter 2. It is a single frame of wrongness that would be almost impossible to photograph and very easy to feel.

The lazy part

9:41▲ ▮
Pocket Notes
Shopping
Oat milk, tomatoes, bread
Just now
Ideas
A timer that turns the lights down when a session starts, then puts them back up at the…
3 h ago
Untitled
Ring Mum back
Yesterday
+

The list. Card 2 shows the two-line clamp and its ellipsis.

9:41▲ ▮
Pocket Notes
No notes yet
Tap the + button to write your first one. Everything you type is saved on this phone, and only on this phone.
+

First launch. Nothing in the database, and still a designed screen.

The app bar and the stay on screen behind the empty state, because they belong to the Scaffold and the empty state is only the content slot.

Try it in Pocket Studio

Three new files and one rewrite. The FAB still adds test notes — that changes next chapter.

  1. Open Pocket StudioProjectsPocket Notes.
  2. Open app/build.gradle.kts and add the wrapped libs.androidx.material.icons.extended dependency. Tap Build.
  3. In the ui package, tap NewKotlin File three times: RelativeTime, NoteCard, EmptyNotes.
  4. Open NoteListScreen.kt and replace the whole file with the chapter 3 version.
  5. Tap Run. If you still have test notes from chapter 2, you get the card list. If not, you get the empty state.
  6. Press the + a few times, then scroll the list up and down. Watch the big title shrink into a normal bar and the bar change colour.
  7. Find the .nestedScroll(bar.nestedScrollConnection) line and comment it out with //. Run again and scroll. The title never moves — that is exactly what the fourth Error Doctor entry looks like from the outside.
  8. Put the line back.

Pocket Notes — end of chapter 3

A complete project. Unzip it, open it in Pocket Studio, and press Run.

Download ZIP
Error Doctor5 common errors
e: file:///.../NoteListScreen.kt:32:15 This material API is experimental and is likely to change or to be removed in the future.
MeansLargeTopAppBar, TopAppBarDefaults and rememberTopAppBarState are not final API yet, and Compose will not let you use them silently. Expect eight or ten of these at once — one per usage.
FixOne annotation above the composable: @OptIn(ExperimentalMaterial3Api::class), immediately before @Composable. It silences all of them at once.
e: file:///.../NoteListScreen.kt:96:9 Unresolved reference 'items'.
Meansitems is an extension function on LazyListScope, not a member of LazyColumn. Importing LazyColumn alone does not bring it along.
FixImport it by name as well: import androidx.compose.foundation.lazy.LazyColumn and import androidx.compose.foundation.lazy.items.
java.lang.IllegalArgumentException: Key "0" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item.
MeansA crash at runtime. Every unsaved Note starts life with id = 0, so a list holding two of them has two rows claiming the same identity.
FixKey by the database id — key = { note -> note.id } — and make sure the list you are drawing came from the database, where Room guarantees the ids are unique.
The large title never shrinks when I scroll, and there is no error at all
MeansYou created the scrollBehavior, passed it to LargeTopAppBar, and never told the Scaffold about the scrolling. The bar is listening to a wire that was never connected.
FixAdd the modifier to the Scaffold itself: modifier = Modifier.nestedScroll(bar.nestedScrollConnection).
e: Call requires API level 26 (current min is 21): java.time.Instant#ofEpochMilli
Meansjava.time only exists on Android 8.0 and above, and your minSdk is lower than that.
FixSet minSdk = 26 in app/build.gradle.kts, which is exactly what this project does and exactly why. If you must support older phones, either use java.text.SimpleDateFormat or turn on core library .
Recap
  • A only builds the rows on screen, so a list of a thousand notes costs the same as a list of twelve.
  • Give items a from the database id. Without it Compose tracks rows by position, and deleting one confuses everything below it.
  • separate by colour, not . The click goes on the inner Column so the respects the rounded corners.
  • Raw milliseconds are for computers. A short when ladder turns them into "Just now", "3 h ago" and "Yesterday".
  • An is a designed screen: a symbol, a headline and one instruction. Check loading first, or you will flash it at people who have hundreds of notes.
  • Next: the + button still adds fake notes. Chapter 4 adds a second screen, a route between them, and an editor that saves when you leave it.