What’s new in the Jetpack Compose August ’25 release
The Jetpack Compose August ’25 release is now stable, bringing version 1.9 of core Compose modules. This update introduces exciting features to make your Android apps more dynamic and performant.
How to use this release:
- Upgrade to version 1.9 for new APIs.
- Includes features like shadows, 2D scrolling, text styling, and better list performance.
- Add this line to your Gradle file to get started:
implementation(platform("androidx.compose:compose-bom:2025.08.00"))
Shadows
Add modern shadow effects to your UI with two new modifiers:
- dropShadow(): Draws a shadow behind your content.
- innerShadow(): Draws a shadow inside the edges of a shape.
- These differ from the existing `shadow()` modifier, which uses a lighting model.
Modifier.dropShadow()
This modifier places a shadow behind your UI element, like a card or button.
- Customize radius, color, and spread.
- Place `dropShadow()` before background modifiers to ensure the shadow appears behind the content.
@Composable
@Preview(showBackground = true)
fun SimpleDropShadowUsage() {
// Track visibility state
val pinkColor = Color(0xFFe91e63)
val purpleColor = Color(0xFF9c27b0)
Box(Modifier.fillMaxSize()) {
Box(
Modifier
.size(200.dp)
.align(Alignment.Center)
.dropShadow(
RoundedCornerShape(20.dp),
dropShadow = DropShadow(
15.dp,
color = pinkColor,
spread = 10.dp,
alpha = 0.5f
)
)
.background(
purpleColor,
shape = RoundedCornerShape(20.dp)
)
)
}
}
Figure 1. Drop shadow drawn all around shape
Modifier.innerShadow()
This modifier adds a shadow inside the shape’s edges, creating a sunken effect.
- Place `innerShadow()` after background or image modifiers, as it draws on top.
- Customize radius, color, spread, and transparency.
@Composable
@Preview(showBackground = true)
fun SimpleInnerShadowUsage() {
val pinkColor = Color(0xFFe91e63)
val purpleColor = Color(0xFF9c27b0)
Box(Modifier.fillMaxSize()) {
Box(
Modifier
.size(200.dp)
.align(Alignment.Center)
.background(
purpleColor,
shape = RoundedCornerShape(20.dp)
)
.innerShadow(
RoundedCornerShape(20.dp),
innerShadow = InnerShadow(
15.dp,
color = Color.Black,
spread = 10.dp,
alpha = 0.5f
)
)
)
}
}
Figure 2. Modifier.innerShadow() applied to a shape
For images, use a separate Box to layer the inner shadow on top:
- Place the image in one Box.
- Add a second Box with `innerShadow()` to overlay the shadow.
@Composable
@Preview(showBackground = true)
fun PhotoInnerShadowExample() {
Box(Modifier.fillMaxSize()) {
val shape = RoundedCornerShape(20.dp)
Box(
Modifier
.size(200.dp)
.align(Alignment.Center)
) {
Image(
painter = painterResource(id = R.drawable.cape_town),
contentDescription = "Image with Inner Shadow",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
.clip(shape)
)
Box(
modifier = Modifier.fillMaxSize()
.innerShadow(
shape,
innerShadow = InnerShadow(15.dp,
spread = 15.dp)
)
)
}
}
}
Figure 3. Inner shadow on top of an image
New Visibility Modifiers
Track when UI elements appear or disappear on the screen with new modifiers:
- Built on the `onLayoutRectChanged` API from Compose UI 1.8.
- Control actions based on how long or how much of an item is visible.
Use `onVisibilityChanged` to trigger actions like playing/pausing videos:
- Triggers when an element’s visibility changes.
- Set minimum visibility duration or fraction (e.g., fully visible for 500ms).
LazyColumn {
items(feedData) { video ->
VideoRow(
video,
Modifier.onVisibilityChanged(minDurationMs = 500, minFractionVisible = 1f) {
visible ->
// Track visibility state
if (visible) video.play() else video.pause()
},
)
}
}
Use `onFirstVisible` to log when an element first appears:
- Ideal for tracking impressions (e.g., ads or content views).
- Specify a minimum visibility duration (e.g., 500ms).
LazyColumn {
items(100) {
Box(
Modifier
// Log impressions when item has been visible for 500ms
.onFirstVisible(minDurationMs = 500) { /* log impression */ }
.clip(RoundedCornerShape(16.dp))
.drawBehind { drawRect(backgroundColor) }
.fillMaxWidth()
.height(100.dp)
)
}
}
Rich Styling in OutputTransformation
Style text input in `BasicTextField` without changing its data:
- Use `TextFieldBuffer.addStyle()` to apply colors or font weights.
- Perfect for formatting phone numbers, credit cards, or other inputs.
- Works only inside an `OutputTransformation`.
// Format a phone number and color the punctuation
val phoneTransformation = OutputTransformation {
// 1234567890 -> (123) 456-7890
if (length == 10) {
insert(0, "(")
insert(4, ") ")
insert(9, "-")
// Color the added punctuation
val gray = Color(0xFF666666)
addStyle(SpanStyle(color = gray), 0, 1)
addStyle(SpanStyle(color = gray), 4, 5)
addStyle(SpanStyle(color = gray), 9, 10)
}
}
BasicTextField(
state = myTextFieldState,
outputTransformation = phoneTransformation
)
LazyLayout
Build custom lazy components with stable APIs:
- `LazyLayoutMeasurePolicy`: Defines how items are measured and placed.
- `LazyLayoutItemProvider`: Manages the list of items.
- `LazyLayoutPrefetchState`: Handles prefetching for performance.
Prefetch Improvements
Make lists and grids scroll faster with new prefetch options:
- `LazyLayoutCacheWindow` lets you prefetch items ahead and retain items behind.
- Customize prefetching by viewport fraction or dp size.
- Improves performance by composing items before they’re visible.
Configure prefetching with `LazyListState`:
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun LazyColumnCacheWindowDemo() {
// Prefetch items 150.dp ahead and retain items 100.dp behind the visible viewport
val dpCacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp)
// Alternatively, prefetch/retain items as a fraction of the list size
// val fractionCacheWindow = LazyLayoutCacheWindow(aheadFraction = 1f, behindFraction = 0.5f)
val state = rememberLazyListState(cacheWindow = dpCacheWindow)
LazyColumn(state = state) {
items(1000) { Text(text = "$it", fontSize = 80.sp) }
}
}
Note: Prefetching may run `LaunchedEffects` or `DisposableEffects` earlier. Avoid using these for visibility tracking. Use `onFirstVisible` or `onVisibilityChanged` instead.
Scroll
2D Scroll APIs
Create layouts that scroll in both directions, like spreadsheets or image viewers:
- `Scrollable2D` enables 2D scrolling and flinging.
- Supports nested scrolling for complex layouts.
- Unlike `Scrollable`, which is single-direction, `Scrollable2D` moves freely in 2D.
val offset = remember { mutableStateOf(Offset.Zero) }
Box(
Modifier.size(150.dp)
.scrollable2D(
state =
rememberScrollable2DState { delta ->
offset.value = offset.value + delta // update the state
delta // indicate that we consumed all the pixels available
}
)
.background(Color.LightGray),
contentAlignment = Alignment.Center,
) {
Text(
"X=${offset.value.x.roundToInt()} Y=${offset.value.y.roundToInt()}",
style = TextStyle(fontSize = 32.sp),
)
}
Scroll Interop Improvements
Improved integration with Android Views for scrolling:
- Use `ViewTreeObserver` to listen to Compose scroll events.
- Fixed incorrect fling velocities between Compose and Views.
- Correct order for nested scroll callbacks in Views.
- Proper nested scrolling with `NestedScrollView` inside `AndroidView`.
Improve Crash Analysis
Debug crashes more easily with detailed stack traces:
- New opt-in API shows composable names and locations in crashes.
- Helps identify and fix crash sources quickly.
- Best for debug builds (not release, due to performance impact).
Enable it in your app’s entry point:
class App : Application() {
override fun onCreate() {
// Enable only for debug flavor to avoid perf regressions in release
Composer.setDiagnosticStackTraceEnabled(BuildConfig.DEBUG)
}
}
New Annotations and Lint Checks
New tools to write safer Compose code:
- New Library: `runtime-annotation` for `@Stable`, `@Immutable`, and `@StableMarker` without needing Compose runtime.
- @RememberInComposition: Marks functions/constructors that need `remember` in composition, with lint check errors.
- @FrequentlyChangingValue: Flags functions/getters causing frequent recompositions, with lint check warnings.
Additional Updates
- AGP/Lint Requirement: Use Android Gradle Plugin (AGP) 8.8.2 or higher for better lint support.
- Context Menu APIs:
- `Modifier.appendTextContextMenuComponents()`: Add custom items to context menus.
- `Modifier.filterTextContextMenuComponents()`: Remove items from context menus.