Skip to content

Commit 3fc82a0

Browse files
Treehugger RobotGerrit Code Review
authored andcommitted
Merge "CacheWindow refill update #2" into androidx-main
2 parents dc419ff + 343da12 commit 3fc82a0

8 files changed

Lines changed: 293 additions & 33 deletions

File tree

compose/foundation/foundation/api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ package androidx.compose.foundation {
151151
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class ComposeFoundationFlags {
152152
property public boolean isBringIntoViewRltBouncyBehaviorInPagerFixEnabled;
153153
property public boolean isCacheWindowForPagerEnabled;
154+
property public boolean isCacheWindowRefillFixEnabled;
154155
property public boolean isDetectTapGesturesImmediateCoroutineDispatchEnabled;
155156
property public boolean isNewContextMenuEnabled;
156157
property public boolean isNonSuspendingPointerInputInClickableEnabled;
@@ -159,6 +160,7 @@ package androidx.compose.foundation {
159160
field public static final androidx.compose.foundation.ComposeFoundationFlags INSTANCE;
160161
field public static boolean isBringIntoViewRltBouncyBehaviorInPagerFixEnabled;
161162
field public static boolean isCacheWindowForPagerEnabled;
163+
field public static boolean isCacheWindowRefillFixEnabled;
162164
field public static boolean isDetectTapGesturesImmediateCoroutineDispatchEnabled;
163165
field public static boolean isNewContextMenuEnabled;
164166
field public static boolean isNonSuspendingPointerInputInClickableEnabled;

compose/foundation/foundation/api/restricted_current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ package androidx.compose.foundation {
151151
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class ComposeFoundationFlags {
152152
property public boolean isBringIntoViewRltBouncyBehaviorInPagerFixEnabled;
153153
property public boolean isCacheWindowForPagerEnabled;
154+
property public boolean isCacheWindowRefillFixEnabled;
154155
property public boolean isDetectTapGesturesImmediateCoroutineDispatchEnabled;
155156
property public boolean isNewContextMenuEnabled;
156157
property public boolean isNonSuspendingPointerInputInClickableEnabled;
@@ -159,6 +160,7 @@ package androidx.compose.foundation {
159160
field public static final androidx.compose.foundation.ComposeFoundationFlags INSTANCE;
160161
field public static boolean isBringIntoViewRltBouncyBehaviorInPagerFixEnabled;
161162
field public static boolean isCacheWindowForPagerEnabled;
163+
field public static boolean isCacheWindowRefillFixEnabled;
162164
field public static boolean isDetectTapGesturesImmediateCoroutineDispatchEnabled;
163165
field public static boolean isNewContextMenuEnabled;
164166
field public static boolean isNonSuspendingPointerInputInClickableEnabled;

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package androidx.compose.foundation.lazy.list
1818

19+
import androidx.compose.foundation.ComposeFoundationFlags.isCacheWindowRefillFixEnabled
1920
import androidx.compose.foundation.ExperimentalFoundationApi
2021
import androidx.compose.foundation.gestures.Orientation
2122
import androidx.compose.foundation.gestures.scrollBy
@@ -45,6 +46,7 @@ import com.google.common.truth.Truth.assertThat
4546
import kotlinx.coroutines.CoroutineScope
4647
import kotlinx.coroutines.launch
4748
import kotlinx.coroutines.runBlocking
49+
import org.junit.Assume
4850
import org.junit.Test
4951
import org.junit.runner.RunWith
5052
import org.junit.runners.Parameterized
@@ -185,7 +187,8 @@ class LazyListCacheWindowTest(orientation: Orientation) :
185187
}
186188

187189
@Test
188-
fun datasetChanged_shouldMakeSureNestedItemsChanged() {
190+
fun datasetChanged_shouldMakeSureNestedItemsChanged_afterScroll() {
191+
Assume.assumeTrue(isCacheWindowRefillFixEnabled)
189192
val items = mutableStateOf(listOf("a", "b", "c", "d", "e"))
190193

191194
rule.setContent {
@@ -264,6 +267,81 @@ class LazyListCacheWindowTest(orientation: Orientation) :
264267
rule.onNodeWithTag("second-nested-e").assertExists() // nested prefetched
265268
}
266269

270+
@Test
271+
fun datasetChanged_shouldMakeSureNestedItemsChanged_noScroll() {
272+
Assume.assumeTrue(isCacheWindowRefillFixEnabled)
273+
val items = mutableStateOf(listOf("a", "b", "c", "d"))
274+
275+
rule.setContent {
276+
@OptIn(ExperimentalFoundationApi::class)
277+
state = rememberLazyListState(cacheWindow = viewportWindow)
278+
LazyColumnOrRow(
279+
Modifier.mainAxisSize(itemsSizeDp * 2f)
280+
.then(
281+
object : RemeasurementModifier {
282+
override fun onRemeasurementAvailable(remeasurement: Remeasurement) {
283+
remeasure = remeasurement
284+
}
285+
}
286+
),
287+
state,
288+
) {
289+
items(items.value, key = { it }) {
290+
if (it == "e" || it == "f") {
291+
val state = rememberLazyListState(cacheWindow = viewportWindow)
292+
LazyRow(
293+
Modifier.mainAxisSize(itemsSizeDp).fillMaxCrossAxis().testTag(it),
294+
state = state,
295+
) {
296+
item {
297+
Spacer(
298+
Modifier.mainAxisSize(itemsSizeDp)
299+
.fillMaxCrossAxis()
300+
.testTag("first-nested-$it")
301+
)
302+
}
303+
304+
item {
305+
Spacer(
306+
Modifier.mainAxisSize(itemsSizeDp)
307+
.fillMaxCrossAxis()
308+
.testTag("second-nested-$it")
309+
)
310+
}
311+
}
312+
} else {
313+
Spacer(
314+
Modifier.mainAxisSize(itemsSizeDp)
315+
.fillMaxCrossAxis()
316+
.testTag(it)
317+
.layout { measurable, constraints ->
318+
val placeable = measurable.measure(constraints)
319+
layout(placeable.width, placeable.height) {
320+
placeable.place(0, 0)
321+
}
322+
}
323+
)
324+
}
325+
}
326+
}
327+
}
328+
329+
rule.onNodeWithTag("a").assertIsDisplayed() // fully visible
330+
rule.onNodeWithTag("b").assertIsDisplayed() // fully visible
331+
rule.onNodeWithTag("c").assertExists() // part of the window
332+
rule.onNodeWithTag("d").assertExists() // part of the window
333+
334+
rule.runOnIdle { items.value = listOf("a", "b", "e", "f", "g", "h") }
335+
rule.waitForIdle()
336+
337+
rule.onNodeWithTag("e").assertExists() // item e will take place of item c
338+
rule.onNodeWithTag("first-nested-e").assertExists() // nested prefetched
339+
rule.onNodeWithTag("second-nested-e").assertExists() // nested prefetched
340+
rule.onNodeWithTag("f").assertExists() // item f will take place of item d
341+
rule.onNodeWithTag("first-nested-f").assertExists() // nested prefetched
342+
rule.onNodeWithTag("second-nested-f").assertExists() // nested prefetched
343+
}
344+
267345
@Test
268346
fun datasetChanged_noScrollHappened_shouldKeepAroundWithinBounds_notCrash() {
269347
val numItems = mutableStateOf(100)

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,14 @@ object ComposeFoundationFlags {
107107
@field:Suppress("MutableBareField")
108108
@JvmField
109109
var isBringIntoViewRltBouncyBehaviorInPagerFixEnabled: Boolean = true
110+
111+
/**
112+
* If this flag is enabled, for lazy layout implementations that use
113+
* [androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow], if the dataset changes, the
114+
* window mechanism will understand that it needs to re-fill the window from scratch. This is
115+
* because there is no good way for the window to know that a possible non-visible item has
116+
* changed. For instance, if C and D are 2 items in the cache window and later they're removed
117+
* from the dataset, the cache window won't know it until it tries to prefetch them.
118+
*/
119+
@field:Suppress("MutableBareField") @JvmField var isCacheWindowRefillFixEnabled = false
110120
}

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListCacheWindowStrategy.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ internal class LazyListCacheWindowScope() : CacheWindowScope {
135135

136136
override fun getLastIndexInLine(lineIndex: Int): Int = lineIndex
137137

138+
override fun getVisibleLineKey(indexInVisibleLines: Int): Any {
139+
return layoutInfo.visibleItemsInfo[indexInVisibleLines].key
140+
}
141+
138142
override fun getLastLineIndex(): Int {
139143
if (totalItemsCount == 0) return InvalidIndex
140144
return totalItemsCount - 1

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridCacheWindowStrategy.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.foundation.gestures.snapping.offsetOnMainAxis
2121
import androidx.compose.foundation.gestures.snapping.sizeOnMainAxis
2222
import androidx.compose.foundation.lazy.layout.CacheWindowLogic
2323
import androidx.compose.foundation.lazy.layout.CacheWindowScope
24+
import androidx.compose.foundation.lazy.layout.CachedItem
2425
import androidx.compose.foundation.lazy.layout.InvalidIndex
2526
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
2627
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
@@ -137,6 +138,15 @@ private class LazyGridCacheWindowScope() : CacheWindowScope {
137138
return tallestItemSize
138139
}
139140

141+
override fun getVisibleLineKey(indexInVisibleLines: Int): Any {
142+
// using the first item key to represent this line.
143+
val laneIndex = indexInVisibleLines + firstVisibleLineIndex
144+
return layoutInfo.visibleItemsInfo
145+
.fastFilter { it.lineIndex == laneIndex }
146+
.firstOrNull()
147+
?.key ?: CachedItem.NoKey
148+
}
149+
140150
override fun getVisibleItemLine(indexInVisibleLines: Int): Int =
141151
firstVisibleLineIndex + indexInVisibleLines
142152

0 commit comments

Comments
 (0)