Skip to content

Commit 5d6ff93

Browse files
authored
Copy Jetpack Compose 1.12.0-alpha02 (#3032)
[CMP-10113](https://youtrack.jetbrains.com/issue/CMP-10113) Merge Jetpack Compose 1.12.0-alpha02 | Group ID | Release Version | Release SHA | Release Build ID | Release Date | | --- | --- | --- | --- | --- | | androidx.compose.animation | 1.12.0-alpha02 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.foundation | 1.12.0-alpha02 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.material | 1.12.0-alpha02 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.material3 | 1.5.0-alpha19 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.material3.adaptive | 1.3.0-beta01 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.remote | 1.0.0-alpha010 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.runtime | 1.12.0-alpha02 | e29a109 | 15316886 | 5/6/2026 | | androidx.compose.ui | 1.12.0-alpha02 | e29a109 | 15316886 | 5/6/2026 | ## Release Notes N/A
2 parents c874bdf + 96f060e commit 5d6ff93

1,263 files changed

Lines changed: 95484 additions & 24215 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ abstract class AndroidXImplPlugin @Inject constructor() : Plugin<Project> {
668668
34 -> "8.1.1"
669669
35 -> "8.6.0"
670670
36 -> "8.9.1"
671+
37 -> "9.1.0"
671672
else -> throw Exception("Unknown compileSdk to minAgpVersion mapping")
672673
}
673674
}

compose/animation/animation-benchmark/integration-tests/macrobenchmark-target/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ android {
3030
}
3131
benchmark {
3232
initWith release
33-
signingConfig signingConfigs.debug
33+
signingConfig = signingConfigs.debug
3434
matchingFallbacks = ['release']
3535
debuggable false
3636
}

compose/animation/animation/src/androidDeviceTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ import kotlinx.coroutines.runBlocking
133133
import kotlinx.coroutines.test.StandardTestDispatcher
134134
import leakcanary.DetectLeaksAfterTestSuccess
135135
import org.junit.Assert.assertNotEquals
136+
import org.junit.Ignore
136137
import org.junit.Rule
137138
import org.junit.Test
138139
import org.junit.rules.RuleChain
@@ -5222,6 +5223,7 @@ class SharedTransitionTest {
52225223
assertEquals(false, scope?.isTransitionActive)
52235224
}
52245225

5226+
@Ignore("b/501503494")
52255227
@Test
52265228
fun testDetachingSharedElementAndReattachingInNewPositionBeforeAnimating() {
52275229
var showMatch by mutableStateOf(false)

compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedTransitionScope.kt

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import androidx.compose.runtime.Immutable
4343
import androidx.compose.runtime.Stable
4444
import androidx.compose.runtime.getValue
4545
import androidx.compose.runtime.key
46-
import androidx.compose.runtime.mutableStateListOf
4746
import androidx.compose.runtime.mutableStateMapOf
4847
import androidx.compose.runtime.mutableStateOf
4948
import androidx.compose.runtime.remember
@@ -1423,7 +1422,7 @@ internal constructor(lookaheadScope: LookaheadScope, val coroutineScope: Corouti
14231422
private var _nullableLookaheadRoot: LayoutCoordinates? = null
14241423

14251424
// TODO: Use MutableObjectList and impl sort
1426-
private val renderers = mutableStateListOf<LayerRenderer>()
1425+
private var renderers: List<LayerRenderer> by mutableStateOf(mutableListOf())
14271426

14281427
// sharedElements are being observed for the edge events of 1) any transition has started,
14291428
// and 2) all transitions are finished. As such, the map containing the key-sharedElement pairs
@@ -1445,18 +1444,17 @@ internal constructor(lookaheadScope: LookaheadScope, val coroutineScope: Corouti
14451444
get() = sharedElements.toMap().values
14461445

14471446
private fun sharedElementsFor(key: Any): SharedElement {
1448-
return sharedElements[key] ?: SharedElement(key, this).also { sharedElements[key] = it }
1447+
return sharedElements.getOrPut(key) { SharedElement(key, this) }
14491448
}
14501449

14511450
internal fun drawInOverlay(scope: ContentDrawScope) {
1452-
// TODO: Sort while preserving the parent child order
1453-
renderers.sortBy {
1454-
if (it.zIndex == 0f && it is SharedElementEntry && it.parentState == null) {
1455-
-1f
1456-
} else it.zIndex
1457-
}
1458-
1459-
renderers.fastForEach { it.drawInOverlay(drawScope = scope) }
1451+
renderers =
1452+
renderers.run {
1453+
@Suppress("ListIterator") // stdlib sort is /only/ available with an iterator
1454+
val sorted = sortedWith(LayerRenderer.LayerRendererComparator)
1455+
sorted.fastForEach { it.drawInOverlay(drawScope = scope) }
1456+
sorted
1457+
}
14601458
}
14611459

14621460
internal fun onEntryRemoved(sharedElementState: SharedElementEntry) {
@@ -1467,7 +1465,7 @@ internal constructor(lookaheadScope: LookaheadScope, val coroutineScope: Corouti
14671465
with(sharedElementState.sharedElement) {
14681466
removeEntry(sharedElementState)
14691467
updateTransitionActiveness()
1470-
renderers.remove(sharedElementState)
1468+
renderers -= sharedElementState
14711469
if (allEntries.isEmpty()) {
14721470
scope.coroutineScope.launch {
14731471
if (allEntries.isEmpty()) {
@@ -1487,24 +1485,29 @@ internal constructor(lookaheadScope: LookaheadScope, val coroutineScope: Corouti
14871485
with(sharedElementState.sharedElement) {
14881486
addEntry(sharedElementState)
14891487
updateTransitionActiveness()
1488+
val renderersList = renderers
14901489
val id =
1491-
renderers.indexOfFirst {
1490+
renderersList.indexOfFirst {
14921491
(it as? SharedElementEntry)?.sharedElement == sharedElementState.sharedElement
14931492
}
1494-
if (id == renderers.size - 1 || id == -1) {
1495-
renderers.add(sharedElementState)
1493+
if (id == -1 || id >= renderersList.size - 1) {
1494+
renderers += sharedElementState
14961495
} else {
1497-
renderers.add(id + 1, sharedElementState)
1496+
renderers = buildList {
1497+
addAll(renderersList.subList(0, id + 1))
1498+
add(sharedElementState)
1499+
addAll(renderersList.subList(id + 1, renderersList.size))
1500+
}
14981501
}
14991502
}
15001503
}
15011504

15021505
internal fun onLayerRendererCreated(renderer: LayerRenderer) {
1503-
renderers.add(renderer)
1506+
renderers += renderer
15041507
}
15051508

15061509
internal fun onLayerRendererRemoved(renderer: LayerRenderer) {
1507-
renderers.remove(renderer)
1510+
renderers -= renderer
15081511
}
15091512

15101513
private class ShapeBasedClip(val clipShape: Shape) : OverlayClip {
@@ -1530,6 +1533,17 @@ internal interface LayerRenderer {
15301533
fun drawInOverlay(drawScope: DrawScope)
15311534

15321535
val zIndex: Float
1536+
1537+
companion object LayerRendererComparator : Comparator<LayerRenderer> {
1538+
override fun compare(a: LayerRenderer, b: LayerRenderer): Int =
1539+
comparator(a).compareTo(comparator(b))
1540+
1541+
// TODO: Sort while preserving the parent child order
1542+
private fun comparator(it: LayerRenderer): Float =
1543+
if (it.zIndex == 0f && it is SharedElementEntry && it.parentState == null) {
1544+
-1f
1545+
} else it.zIndex
1546+
}
15331547
}
15341548

15351549
private val DefaultSpring =

compose/compiler/OWNERS

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Bug component: 343210
22
chuckj@google.com
3-
lelandr@google.com
43
anbailey@google.com
54
ashikov@google.com
6-
per-file settings.gradle = dustinlam@google.com, rahulrav@google.com
5+
derekx@google.com
6+
7+
per-file settings.gradle = rahulrav@google.com

compose/foundation/foundation/api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ package androidx.compose.foundation {
155155
property public boolean isBasicTextFieldMinSizeOptimizationEnabled;
156156
property public boolean isCacheWindowForPagerEnabled;
157157
property public boolean isConcurrentTextFieldSelectionFixEnabled;
158+
property public boolean isDragNodeOffsetDoubleCountingFixEnabled;
158159
property public boolean isInheritedTextStyleEnabled;
159160
property public boolean isNewContextMenuEnabled;
160161
property public boolean isPausableCompositionInPrefetchEnabled;
@@ -167,6 +168,7 @@ package androidx.compose.foundation {
167168
field public static boolean isBasicTextFieldMinSizeOptimizationEnabled;
168169
field public static boolean isCacheWindowForPagerEnabled;
169170
field public static boolean isConcurrentTextFieldSelectionFixEnabled;
171+
field public static boolean isDragNodeOffsetDoubleCountingFixEnabled;
170172
field public static boolean isInheritedTextStyleEnabled;
171173
field public static boolean isNewContextMenuEnabled;
172174
field public static boolean isPausableCompositionInPrefetchEnabled;

compose/foundation/foundation/api/restricted_current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ package androidx.compose.foundation {
155155
property public boolean isBasicTextFieldMinSizeOptimizationEnabled;
156156
property public boolean isCacheWindowForPagerEnabled;
157157
property public boolean isConcurrentTextFieldSelectionFixEnabled;
158+
property public boolean isDragNodeOffsetDoubleCountingFixEnabled;
158159
property public boolean isInheritedTextStyleEnabled;
159160
property public boolean isNewContextMenuEnabled;
160161
property public boolean isPausableCompositionInPrefetchEnabled;
@@ -167,6 +168,7 @@ package androidx.compose.foundation {
167168
field public static boolean isBasicTextFieldMinSizeOptimizationEnabled;
168169
field public static boolean isCacheWindowForPagerEnabled;
169170
field public static boolean isConcurrentTextFieldSelectionFixEnabled;
171+
field public static boolean isDragNodeOffsetDoubleCountingFixEnabled;
170172
field public static boolean isInheritedTextStyleEnabled;
171173
field public static boolean isNewContextMenuEnabled;
172174
field public static boolean isPausableCompositionInPrefetchEnabled;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.benchmark.text
18+
19+
import androidx.benchmark.junit4.BenchmarkRule
20+
import androidx.benchmark.junit4.measureRepeated
21+
import androidx.compose.foundation.text.input.TextFieldState
22+
import androidx.test.filters.LargeTest
23+
import org.junit.Rule
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.junit.runners.Parameterized
27+
28+
@LargeTest
29+
@RunWith(Parameterized::class)
30+
class TextFieldStateBenchmark(val textLength: Int) {
31+
32+
@get:Rule val benchmarkRule = BenchmarkRule()
33+
34+
companion object {
35+
@JvmStatic
36+
@Parameterized.Parameters(name = "length={0}")
37+
fun initParameters(): Array<Any> = arrayOf(10, 500)
38+
}
39+
40+
private fun createTestState(): TextFieldState {
41+
return TextFieldState("A".repeat(textLength))
42+
}
43+
44+
@Test
45+
fun edit_empty() {
46+
val state = createTestState()
47+
benchmarkRule.measureRepeated { state.edit {} }
48+
}
49+
50+
@Test
51+
fun edit_noChangeAfterEdit() {
52+
val state = createTestState()
53+
val index = state.text.length / 2
54+
benchmarkRule.measureRepeated {
55+
state.edit {
56+
replace(index, index, "B")
57+
replace(index, index + 1, "")
58+
}
59+
}
60+
}
61+
62+
@Test
63+
fun edit_singleReplace_noGapMove() {
64+
val state = createTestState()
65+
val index = state.text.length / 2
66+
benchmarkRule.measureRepeated {
67+
state.edit { replace(index, index, "B") }
68+
runWithMeasurementDisabled {
69+
// Delete the inserted text
70+
state.edit { replace(index, index + 1, "") }
71+
}
72+
}
73+
}
74+
75+
@Test
76+
fun edit_singleReplace_gapMove() {
77+
val state = createTestState()
78+
val insertionIndex = state.text.length / 2
79+
val gapIndex = (state.text.length / 4) * 3
80+
benchmarkRule.measureRepeated {
81+
runWithMeasurementDisabled {
82+
// Move the gap to the gapIndex.
83+
state.edit { replace(gapIndex, gapIndex, "B") }
84+
}
85+
state.edit { replace(insertionIndex, insertionIndex, "B") }
86+
runWithMeasurementDisabled {
87+
// Delete the inserted text.
88+
state.edit {
89+
replace(insertionIndex, insertionIndex + 1, "")
90+
replace(gapIndex, gapIndex + 1, "")
91+
}
92+
}
93+
}
94+
}
95+
}

compose/foundation/foundation/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ plugins {
3535

3636
androidXMultiplatform {
3737
androidLibrary {
38-
compileSdk = 35
38+
compileSdk = 37
3939
namespace = "androidx.compose.foundation"
4040
optimization {
4141
it.consumerKeepRules.publish = true

compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import androidx.compose.ui.viewinterop.AndroidView
5757
import androidx.test.filters.MediumTest
5858
import com.google.common.truth.Truth.assertThat
5959
import kotlin.collections.removeFirst as removeFirstKt
60+
import kotlin.random.Random
6061
import kotlin.test.assertTrue
6162
import kotlinx.coroutines.runBlocking
6263
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -774,6 +775,62 @@ class LazyListPinnableContainerTest(val useLookaheadScope: Boolean) {
774775

775776
rule.onNodeWithTag("1").assertExists().assertIsNotDisplayed().assertIsPlaced()
776777
}
778+
779+
@Test
780+
fun pinnedItemsAreLaidOutInOrderWhenScrolledOut() {
781+
val state = LazyListState()
782+
val pinnableContainerByIndex = mutableMapOf<Int, PinnableContainer>()
783+
val pinHandlesByIndex = mutableMapOf<Int, PinnedHandle>()
784+
val shuffelSeed = 0xCAFEBABE
785+
786+
val listSize = 20
787+
val topPinRange = 0 until listSize / 2
788+
val bottomPinRange = listSize / 2 until listSize
789+
790+
rule.setContentParameterized {
791+
LazyColumn(Modifier.size(itemSize * 10), state = state) {
792+
items(listSize) { index ->
793+
pinnableContainerByIndex[index] = LocalPinnableContainer.current!!
794+
Item(index)
795+
}
796+
}
797+
}
798+
799+
rule.waitForIdle()
800+
801+
fun validateLayoutOrder(pinRange: IntRange) {
802+
var prevTop: Float? = null
803+
for (i in pinRange) {
804+
val top = rule.onNodeWithTag("$i").fetchSemanticsNode().positionInRoot.y
805+
if (prevTop != null) {
806+
assertThat(top).isGreaterThan(prevTop)
807+
}
808+
prevTop = top
809+
}
810+
}
811+
812+
topPinRange
813+
.toMutableList()
814+
.apply { shuffle(Random(shuffelSeed)) }
815+
.forEach { i -> pinHandlesByIndex[i] = pinnableContainerByIndex[i]!!.pin() }
816+
817+
rule.runOnIdle { runBlocking { state.scrollToItem(listSize - 1) } }
818+
rule.waitForIdle()
819+
validateLayoutOrder(topPinRange)
820+
821+
topPinRange.forEach { i -> pinHandlesByIndex[i]!!.release() }
822+
823+
pinHandlesByIndex.clear()
824+
825+
bottomPinRange
826+
.toMutableList()
827+
.apply { shuffle(Random(shuffelSeed)) }
828+
.forEach { i -> pinHandlesByIndex[i] = pinnableContainerByIndex[i]!!.pin() }
829+
830+
rule.runOnIdle { runBlocking { state.scrollToItem(0) } }
831+
rule.waitForIdle()
832+
validateLayoutOrder(bottomPinRange)
833+
}
777834
}
778835

779836
/**

0 commit comments

Comments
 (0)