Jetpack Compose 1.8 rolls out handy features like Autofill integration, slick Text enhancements including auto-sizing and new overflow options, and efficient Visibility Tracking, alongside the usual performance boosts. Updating your dependencies brings faster UI and useful new tools to your Android development workflow.

Alright, let’s dive into the latest goodies Google has packed into Jetpack Compose with the 1.8 release (around April ’25). If you like your apps smooth and your coding life easier, pay attention!

Performance Keeps Getting Better

Google’s still tuning the engine! They’re reporting continued improvements in scroll performance and startup times compared to previous versions. Often, just updating the Compose BOM gets you these benefits.

// build.gradle.kts (Module level)

dependencies {
    // Use the April '25 BOM for Compose 1.8 features
    val composeBom = platform("androidx.compose:compose-bom:2025.04.01")
    implementation(composeBom)
    androidTestImplementation(composeBom)

    // Core Compose dependencies (versions managed by BOM)
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.foundation:foundation") // Needed for BasicText, Autofill
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3") // Use latest M3

    // Other dependencies...
}

These speed-ups come from ongoing optimizations in areas like text, input handling, lazy lists, and graphics, plus the continued refinement of the Modifier.Node system.

Autofill Arrives in Compose

Tired of users manually typing login details or addresses? Compose 1.8 integrates with Android’s Autofill framework. Setting it up is pretty simple:

  1. Tell the system what the field is: Use Modifier.semantics with the right ContentType.
  2. Handle saving (if needed): Often automatic on navigation, or use AutofillManager.commit() explicitly (e.g., on button press).
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.ContentType

@Composable
fun LoginForm() {
    val usernameState = remember { mutableStateOf("") }
    val passwordState = remember { mutableStateOf("") }

    val autofill = LocalAutofill.current
    val autofillTree = LocalAutofillTree.current

    // Register the fields within the Autofill tree
    autofillTree += AutofillNode(
        autofillTypes = listOf(AutofillType.Username),
        onFill = { usernameState.value = it }
    )
    autofillTree += AutofillNode(
        autofillTypes = listOf(AutofillType.Password),
        onFill = { passwordState.value = it }
    )

    Column {
        BasicTextField(
            value = usernameState.value,
            onValueChange = { usernameState.value = it },
            modifier = Modifier
                .semantics { contentType = ContentType.Username }
                .autofill( // Connect node to field
                    autofillTypes = listOf(AutofillType.Username),
                    onFill = { usernameState.value = it }
                )
        )
        BasicTextField(
            value = passwordState.value,
            onValueChange = { passwordState.value = it },
            modifier = Modifier
                .semantics { contentType = ContentType.Password }
                .autofill(
                    autofillTypes = listOf(AutofillType.Password),
                    onFill = { passwordState.value = it }
                )
        )

        Button(onClick = {
            // Explicitly trigger save when user submits
            autofill?.requestAutofillForNode(autofillTree.children.last())
            // Or potentially commit if needed, check docs for specifics
            // autofill?.commit()
        }) {
            Text("Login")
        }
    }
}

Check the official Autofill in Compose documentation for the full details.

Text Enhancements

Compose 1.8 brings some nice upgrades for text handling:

Auto-Sizing Text

Need text to shrink to fit its container? BasicText (in androidx.compose.foundation) now has an overload supporting autoSize configuration. You can set min/max font sizes and the granularity (step size) for resizing. Material Text support is expected to follow.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun AutoSizingTextDemo() {
    Box(modifier = Modifier.size(width = 150.dp, height = 50.dp), contentAlignment = Alignment.Center) {
        BasicText(
            text = "This text might need to shrink",
            style = TextStyle(
                fontSize = 24.sp // Start size
            ),
            autoSize = true,
            minTextSize = 12.sp, // Smallest allowed size
            maxTextSize = 24.sp, // Largest allowed size (matches initial style)
            stepGranularity = 1.sp // How much to shrink each step
        )
    }
}

More Text Overflow Options

Beyond the classic end ellipsis (...), you can now use TextOverflow.StartEllipsis (...ext) or TextOverflow.MiddleEllipsis (Te...xt) to truncate text differently.

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp

@Composable
fun OverflowDemo() {
    val longText = "This is a very long piece of text that won't fit."
    Column {
        Text(longText, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) // Default
        Text(longText, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.StartEllipsis)
        Text(longText, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.MiddleEllipsis)
    }
}

Efficient Visibility Tracking

Need to know when a composable enters or leaves the screen, perhaps for analytics or triggering actions? The new Modifier.onLayoutRectChanged (in androidx.compose.ui) is a more performant way to do this than onGloballyPositioned, especially inside lazy lists (LazyColumnLazyRow). It allows debouncing and throttling callbacks to avoid excessive work during scrolling.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onLayoutRectChanged
import androidx.compose.ui.unit.dp

@Composable
fun VisibilityTrackingList() {
    LazyColumn {
        items(100) { index ->
            var isVisible by remember { mutableStateOf(false) }

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .onLayoutRectChanged {
                        // isCompletelyVisible is true if the entire layout bounds are visible
                        // isVisible is true if any part is visible
                        val currentlyVisible = it.isVisible
                        if (currentlyVisible != isVisible) {
                            isVisible = currentlyVisible
                            println("Item $index visibility changed: $isVisible")
                            // Trigger analytics or other actions here
                        }
                    }
            ) {
                Text("Item $index - Visible: $isVisible")
            }
        }
    }
}

Modifier.Node: Custom Modifiers Done Right

Speaking of Modifier.Node, it’s the modern way to build custom modifiers. If you were using Modifier.composed, it’s time to look at the new guidance. It’s more efficient and gives you finer control.

Taming Stability

Sometimes Compose recompiles more than you’d like because it can’t tell if a class from an external library (or even your own code!) is ‘stable’. Compose Compiler 1.5.5+ (used with recent Compose versions) introduced a way to provide a configuration file listing classes you know are stable. This avoids unnecessary recompositions without needing wrapper classes. Check the stability configuration docs for the how-to.

Smarter Recomposition & Strong Skipping

The compiler’s getting smarter. It now generates more efficient code, sometimes skipping state tracking if a value is never read or only read once.

There’s also an experimental feature called “strong skipping mode” (aiming for stability around Compose 1.7/1.8). Normally, if a composable takes an ‘unstable’ parameter, it always recomposes if the parent does. Strong skipping relaxes this, allowing skips even with unstable params if the instance hasn’t changed. It also auto-remembers lambdas capturing unstable values. This makes Compose behave more like you’d intuitively expect, reducing recomposition surprises. You can experiment with it now.

Text Layout Tweaks: Bye-Bye Extra Padding

One subtle but impactful change (default since Jan ’24 / Compose 1.6+) is includeFontPadding now being false by default for Text composables. This legacy setting added extra space above/below text based on font metrics. Turning it off makes Compose text align better with designs from tools like Figma right out of the box. Be aware this might cause small shifts in your UI and screenshot tests after updating!

@Composable
fun MyTextComponent(text: String) {
    Text(
        text = text,
        // includeFontPadding = false // This is now the default!
        style = MaterialTheme.typography.bodyLarge
    )
}

If you relied on that old padding, you might need to add manual padding modifiers.

What Else?

Keep Your Project Updated

To grab these goodies, keep your Compose dependencies fresh using the BOM.

// build.gradle.kts (Module level)

android {
    // ...
    buildFeatures {
        compose = true
    }
    composeOptions {
        // Verify compatible Kotlin compiler extension version for BOM 2025.04.01
        kotlinCompilerExtensionVersion = "1.5.15" // Example, check official docs!
    }
    kotlinOptions {
        jvmTarget = "1.8" // Or higher
    }
}

dependencies {
    val composeBom = platform("androidx.compose:compose-bom:2025.04.01")
    implementation(composeBom)
    // ... other compose dependencies
}

For more tips on performance, check out guides like this Compose Multiplatform Performance Guide.

Staying updated means better, faster apps. Happy composing!

0 0 votes
Article Rating

Leave a Reply

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x