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. */