Do less recompositions when the sliding window updates
Previously each LazyListItemsProvider/LazyGridItemsProvider recreation was causing the full LazyList/LazyGrid/LazyLayout recomposition. It is unnecessary, the only work needed is to call state.updateScrollPositionIfTheFirstItemWasMoved().
Test: New tests in LazyListTest and LazyGridTest
Change-Id: I8acb28c2e35c8eb71827531a56d7e83168450d5e
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
index 3e4f72b..ba84f74 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
@@ -1069,5 +1069,39 @@
}
}
+ @Test
+ fun scrollingALotDoesntCauseLazyLayoutRecomposition() {
+ var recomposeCount = 0
+ lateinit var state: LazyGridState
+
+ rule.setContentWithTestViewConfiguration {
+ state = rememberLazyGridState()
+ LazyVerticalGrid(
+ GridCells.Fixed(1),
+ Modifier.composed {
+ recomposeCount++
+ Modifier
+ }.size(100.dp),
+ state
+ ) {
+ items(1000) {
+ Spacer(Modifier.size(100.dp))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertThat(recomposeCount).isEqualTo(1)
+
+ runBlocking {
+ state.scrollToItem(100)
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertThat(recomposeCount).isEqualTo(1)
+ }
+ }
+
// TODO: add tests for the cache logic
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 234cd3d..6192186 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -1683,6 +1683,39 @@
}
}
+ @Test
+ fun scrollingALotDoesntCauseLazyLayoutRecomposition() {
+ var recomposeCount = 0
+ lateinit var state: LazyListState
+
+ rule.setContentWithTestViewConfiguration {
+ state = rememberLazyListState()
+ LazyColumnOrRow(
+ Modifier.composed {
+ recomposeCount++
+ Modifier
+ },
+ state
+ ) {
+ items(1000) {
+ Spacer(Modifier.size(10.dp))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(recomposeCount).isEqualTo(1)
+
+ runBlocking {
+ state.scrollToItem(100)
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(recomposeCount).isEqualTo(1)
+ }
+ }
+
// ********************* END OF TESTS *********************
// Helper functions, etc. live below here
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index 08ed113..e80d43d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -109,10 +109,7 @@
state.prefetchPolicy = rememberLazyLayoutPrefetchPolicy()
val innerState = rememberLazyLayoutState().also { state.innerState = it }
- val itemsProvider = stateOfItemsProvider.value
- if (itemsProvider.itemsCount > 0) {
- state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
- }
+ ScrollPositionUpdater(stateOfItemsProvider, state)
LazyLayout(
modifier = modifier
@@ -152,6 +149,19 @@
)
}
+/** Extracted to minimize the recomposition scope */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun ScrollPositionUpdater(
+ stateOfItemsProvider: State<LazyGridItemsProvider>,
+ state: LazyGridState
+) {
+ val itemsProvider = stateOfItemsProvider.value
+ if (itemsProvider.itemsCount > 0) {
+ state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun rememberLazyGridMeasurePolicy(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
index 7ea9ee4..8953fbd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemsProviderImpl.kt
@@ -45,7 +45,7 @@
val latestContent = rememberUpdatedState(content)
val nearestItemsRangeState = remember(state) {
mutableStateOf(
- calculateNearestItemsRange(state.firstVisibleItemIndex)
+ calculateNearestItemsRange(state.firstVisibleItemIndexNonObservable.value)
)
}
LaunchedEffect(nearestItemsRangeState) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
index 715dfaf..291d2b2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/list/LazyList.kt
@@ -107,10 +107,7 @@
state.prefetchPolicy = rememberLazyLayoutPrefetchPolicy()
val innerState = rememberLazyLayoutState().also { state.innerState = it }
- val itemsProvider = stateOfItemsProvider.value
- if (itemsProvider.itemsCount > 0) {
- state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
- }
+ ScrollPositionUpdater(stateOfItemsProvider, state)
LazyLayout(
modifier = modifier
@@ -149,6 +146,18 @@
)
}
+/** Extracted to minimize the recomposition scope */
+@Composable
+private fun ScrollPositionUpdater(
+ stateOfItemsProvider: State<LazyListItemsProvider>,
+ state: LazyListState
+) {
+ val itemsProvider = stateOfItemsProvider.value
+ if (itemsProvider.itemsCount > 0) {
+ state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
+ }
+}
+
@Composable
private fun rememberLazyListMeasurePolicy(
/** State containing the items provider of the list. */