Skip to main content

Command Palette

Search for a command to run...

Jetpack Compose: Text Inputs, State, Buttons & Layouts Getting Interactive!

Published
7 min readView as Markdown
Jetpack Compose: Text Inputs, State, Buttons & Layouts
Getting Interactive!

Welcome back to my Jetpack Journey! 🚀
Until now, we’ve explored how to display text, style it, scroll it, and even color it like a rainbow. But now it’s time to take things up a notch because what’s an app without a little interaction?

In this next phase, I dove into the world of text inputs, buttons, and state management. I learned how to make my UI respond to users typing in text fields, clicking buttons, opening links, and more.

This post is all about getting our Composables to do things and not just sit there looking pretty. So if you’re ready to make your Compose app come alive, let’s jump right in.

OutlinedTextField - Let's Talk Input!

Text input in Compose is super straightforward and surprisingly good-looking right out of the box. Meet OutlinedTextField: a sleek Material component that lets users type in data without you pulling your hair out over XML configs.

Here’s the basic setup:

var text by remember { mutableStateOf("") }

OutlinedTextField(
    value = text,
    onValueChange = { text = it },
    label = { Text("Enter your name") }
)

What's Happening Here?

  • text is your state variable holding the input value.

  • remember + mutableStateOf("") lets the Composable remember the value across recompositions.

  • onValueChange updates the value every time the user types.

That’s it. No TextWatcher, no EditText, no chaos. Just clean, modern, reactive code.

Understanding remember and mutableStateOf

Think of remember as Compose’s way of saying:

“Hey, keep this value around while I redraw the screen.”

It works perfectly for storing values during a single composition lifecycle. But once the screen rotates or the activity is recreated… poof 💨 gone.

That’s where rememberSaveable steps in. But first:

var username by remember { mutableStateOf("") }

This line means:

  • mutableStateOf("") creates a reactive state holder.

  • remember ensures it sticks around between recompositions.

Now, if you type something in a TextField and something else causes the screen to recompose (like a button click), your text won’t disappear — thanks to remember.

But Wait… What About rememberSaveable?

Sometimes you want your input to survive configuration changes like rotation.

var username by rememberSaveable { mutableStateOf("") }

This is just like remember, but with a built-in safety net, it actually saves the state in a Bundle, so your data sticks around even after a full activity recreation.

Think of rememberSaveable as remember with extra muscle.
(Or like remember had a big brother who’s really into persistence 😅)

Selectable Text - Because Sometimes Users Like to Copy Stuff

By default, text in Compose isn’t selectable (unlike that one classmate who is always available for group projects 😅). But Compose gives us a really simple way to change that.

Use SelectionContainer:

SelectionContainer {
    Text("You can select and copy this text!")
}

Wrap your Text inside a SelectionContainer, and boom users can now select, copy, and feel empowered.

Want only part of the text to be selectable? You can get even fancier.

Partially Selectable Text

When you want only certain parts of a UI to be selectable, Compose’s building blocks help out again. Think of a screen where the label isn’t selectable, but the actual data is.

EditColumn {
    Text("Name:")
    SelectionContainer {
        Text("Zohaib Khan")
    }
}

Only the second line is selectable now — clean and intentional. No Java hacks or long XML selectors. Just Compose being cool 😎

Want to add clickable links to a piece of text? That’s where AnnotatedString steps in — kind of like Compose's way of putting metadata inside your text.

val annotatedText = buildAnnotatedString {
    append("Visit ")
    pushStringAnnotation(tag = "URL", annotation = "https://developer.android.com")
    withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
        append("Android Docs")
    }
    pop()
}

Now you can use ClickableText to handle the click event:

val uriHandler = LocalUriHandler.current

ClickableText(
    text = annotatedText,
    onClick = { offset ->
        annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
            .firstOrNull()?.let {
                uriHandler.openUri(it.item)
            }
    }
)

What's happening:

  • You're building a string with metadata (annotation).

  • You assign it a URL tag and style it.

  • On click, you extract the annotation and handle it using LocalUriHandler.

Boom!! clickable, styled links inside your text.
Great for “Terms and Conditions”, “Read More”, or that link to your cat’s Instagram 😹.

Buttons - Tap, Click, Do Magic

In Jetpack Compose, buttons aren’t just UI elements, they’re the spark that triggers user interaction. Whether it's submitting a form or opening a portal to the next screen (or maybe just incrementing a counter), buttons are where it all begins.

Basic Filled Button

The default Button in Compose is filled, meaning it has a solid background and is ready to grab attention.

Button(onClick = { /* Your action here */ }) {
    Text("Click Me!")
}

Tip: Whatever you place inside the button block, text, icon, or both gets displayed inside the button.

Let’s add some life to our button using state:

var count by rememberSaveable { mutableStateOf(0) }

Button(onClick = { count++ }) {
    Text("Clicked $count times")
}

Here’s what’s happening:

  • rememberSaveable stores state across configuration changes (like rotation).

  • mutableStateOf allows your UI to react when the value changes.

  • Every button click increases the counter, and Compose automagically updates the UI.

No XML. No findViewById. No weird workarounds.
Just clean, declarative, and joyful development.

Want More Button Styles?

  • OutlinedButton: Same shape, but with a border instead of a filled background.

  • TextButton: A simple text-only button, good for links or subtle actions.

OutlinedButton(onClick = { /* ... */ }) {
    Text("Outlined Button")
}

TextButton(onClick = { /* ... */ }) {
    Text("Text Button")
}

Each of them supports the same content structure (Text, Icon, etc.) — so you can mix and match based on the vibe of your UI.

Customize with Modifiers

You can style your button using Modifier, just like everything else in Compose.

Button(
    onClick = { /* action */ },
    modifier = Modifier
        .padding(8.dp)
        .fillMaxWidth()
) {
    Text("Full Width Button")
}

Need rounded corners? Colors? Elevation? You can theme it up with ButtonDefaults too. (We’ll explore those when we dive into Material theming.)

Layouts - The Blueprint of Your UI

In Jetpack Compose, everything is a composable even layout containers. Want to stack elements vertically or horizontally? You use Column and Row. Want to overlap elements? Use Box.

Let’s break down the basics:

Column - Stack Vertically

Column arranges its children one after the other vertically (like a vertical LinearLayout).

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Text("Item 1")
    Text("Item 2")
    Text("Item 3")
}

Use verticalArrangement to control spacing between items.
Use horizontalAlignment to align children (start, center, end).

Row - Side by Side

Row arranges elements horizontally (like a horizontal LinearLayout).

Row(
    modifier = Modifier.fillMaxWidth(),
    horizontalArrangement = Arrangement.SpaceEvenly,
    verticalAlignment = Alignment.CenterVertically
) {
    Text("Left")
    Text("Center")
    Text("Right")
}

Think of it as laying things out from left to right.

Arrangement & Alignment

Compose gives you fine control over positioning with:

  • Arrangement: Controls spacing between items

  • Alignment: Controls where items go in the parent container

DirectionArrangementAlignment
ColumnverticalArrangementhorizontalAlignment
RowhorizontalArrangementverticalAlignment

You can use options like:

  • Arrangement.Top, Center, Bottom, SpaceBetween, SpaceEvenly

  • Alignment.Start, End, CenterHorizontally, etc.

Box - Layering & Freeform Alignment

Box allows elements to overlap or be freely positioned inside it.

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.LightGray)
) {
    Text(
        "Top Start",
        modifier = Modifier.align(Alignment.TopStart)
    )

    Text(
        "Bottom End",
        modifier = Modifier.align(Alignment.BottomEnd)
    )
}

You can layer elements on top of each other or align them anywhere using .align().

Box is great for:

  • Background images + text overlays

  • Tooltips

  • Floating buttons

  • Custom component design

Wrapping It All Up

In just a short span, we’ve covered a lot, from capturing user input with OutlinedTextField, to managing state with remember, to creating clickable links and buttons that actually do stuff, and finally, laying it all out with Row, Column, and Box.

We’re no longer just displaying things but we’re building real, interactive UIs.

What once felt like a steep learning curve is now starting to look like a playground. And the best part? Every new concept clicks a little faster than the last. That’s the beauty of Compose, it grows with you.

I’m learning this in real time, so I don’t know exactly what’s coming next but I can already tell it’s gonna get even more exciting.

So stay tuned, because the next post might just be the one where everything starts feeling like second nature. Until then, keep composin’!

More from this blog

mzohaib

26 posts