[go: nahoru, domu]

Refactor internal LazyLayout out of lazy lists

Relnote: n/a
Bug: 166591700
Test: ran tests in foundation.lazy
Change-Id: Ia8ac4d18a9a33a41a96f9ca63a4747d787ec7d3e
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index f41809d..110a916 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -1,6 +1,10 @@
 // Baseline format: 1.0
 RemovedClass: androidx.compose.foundation.gestures.RelativeVelocityTrackerKt:
     Removed class androidx.compose.foundation.gestures.RelativeVelocityTrackerKt
+RemovedClass: androidx.compose.foundation.lazy.LazyListItemContentFactoryKt:
+    Removed class androidx.compose.foundation.lazy.LazyListItemContentFactoryKt
+RemovedClass: androidx.compose.foundation.lazy.LazyListPrefetcher_androidKt:
+    Removed class androidx.compose.foundation.lazy.LazyListPrefetcher_androidKt
 
 
 RemovedMethod: androidx.compose.foundation.ImageKt#Image(androidx.compose.ui.graphics.ImageBitmap, String, androidx.compose.ui.Modifier, androidx.compose.ui.Alignment, androidx.compose.ui.layout.ContentScale, float, androidx.compose.ui.graphics.ColorFilter):
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 8234736..ab96527 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -367,9 +367,6 @@
   public final class LazyListHeadersKt {
   }
 
-  public final class LazyListItemContentFactoryKt {
-  }
-
   public interface LazyListItemInfo {
     method public int getIndex();
     method public Object getKey();
@@ -398,9 +395,6 @@
   public final class LazyListMeasureKt {
   }
 
-  public final class LazyListPrefetcher_androidKt {
-  }
-
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
@@ -448,6 +442,25 @@
 
 }
 
+package androidx.compose.foundation.lazy.layout {
+
+  public final class LazyLayoutItemContentFactoryKt {
+  }
+
+  public final class LazyLayoutKt {
+  }
+
+  public final class LazyLayoutPrefetchPolicyKt {
+  }
+
+  public final class LazyLayoutPrefetcher_androidKt {
+  }
+
+  public final class LazyLayoutStateKt {
+  }
+
+}
+
 package androidx.compose.foundation.selection {
 
   public final class SelectableGroupKt {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 3f38d0a..d5b6b12 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -412,9 +412,6 @@
   public final class LazyListHeadersKt {
   }
 
-  public final class LazyListItemContentFactoryKt {
-  }
-
   public interface LazyListItemInfo {
     method public int getIndex();
     method public Object getKey();
@@ -443,9 +440,6 @@
   public final class LazyListMeasureKt {
   }
 
-  public final class LazyListPrefetcher_androidKt {
-  }
-
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
@@ -494,6 +488,25 @@
 
 }
 
+package androidx.compose.foundation.lazy.layout {
+
+  public final class LazyLayoutItemContentFactoryKt {
+  }
+
+  public final class LazyLayoutKt {
+  }
+
+  public final class LazyLayoutPrefetchPolicyKt {
+  }
+
+  public final class LazyLayoutPrefetcher_androidKt {
+  }
+
+  public final class LazyLayoutStateKt {
+  }
+
+}
+
 package androidx.compose.foundation.selection {
 
   public final class SelectableGroupKt {
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index f41809d..110a916 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -1,6 +1,10 @@
 // Baseline format: 1.0
 RemovedClass: androidx.compose.foundation.gestures.RelativeVelocityTrackerKt:
     Removed class androidx.compose.foundation.gestures.RelativeVelocityTrackerKt
+RemovedClass: androidx.compose.foundation.lazy.LazyListItemContentFactoryKt:
+    Removed class androidx.compose.foundation.lazy.LazyListItemContentFactoryKt
+RemovedClass: androidx.compose.foundation.lazy.LazyListPrefetcher_androidKt:
+    Removed class androidx.compose.foundation.lazy.LazyListPrefetcher_androidKt
 
 
 RemovedMethod: androidx.compose.foundation.ImageKt#Image(androidx.compose.ui.graphics.ImageBitmap, String, androidx.compose.ui.Modifier, androidx.compose.ui.Alignment, androidx.compose.ui.layout.ContentScale, float, androidx.compose.ui.graphics.ColorFilter):
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 8234736..ab96527 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -367,9 +367,6 @@
   public final class LazyListHeadersKt {
   }
 
-  public final class LazyListItemContentFactoryKt {
-  }
-
   public interface LazyListItemInfo {
     method public int getIndex();
     method public Object getKey();
@@ -398,9 +395,6 @@
   public final class LazyListMeasureKt {
   }
 
-  public final class LazyListPrefetcher_androidKt {
-  }
-
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
@@ -448,6 +442,25 @@
 
 }
 
+package androidx.compose.foundation.lazy.layout {
+
+  public final class LazyLayoutItemContentFactoryKt {
+  }
+
+  public final class LazyLayoutKt {
+  }
+
+  public final class LazyLayoutPrefetchPolicyKt {
+  }
+
+  public final class LazyLayoutPrefetcher_androidKt {
+  }
+
+  public final class LazyLayoutStateKt {
+  }
+
+}
+
 package androidx.compose.foundation.selection {
 
   public final class SelectableGroupKt {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index c0dfc19..4673083 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy
 
 import android.os.Build
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
@@ -202,6 +203,33 @@
     }
 
     @Test
+    fun changeItemsCountAndScrollImmediately() {
+        lateinit var state: LazyListState
+        var count by mutableStateOf(100)
+        val composedIndexes = mutableListOf<Int>()
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(Modifier.fillMaxWidth().height(10.dp), state) {
+                items(count) { index ->
+                    composedIndexes.add(index)
+                    Box(Modifier.size(20.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            composedIndexes.clear()
+            count = 10
+            runBlocking(AutoTestFrameClock()) {
+                state.scrollToItem(50)
+            }
+            composedIndexes.forEach {
+                assertThat(it).isLessThan(count)
+            }
+            assertThat(state.firstVisibleItemIndex).isEqualTo(9)
+        }
+    }
+    @Test
     fun changingDataTest() {
         val dataLists = listOf(
             (1..3).toList(),
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
similarity index 74%
rename from compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
index 6e219f3..4d2f45d 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
@@ -14,40 +14,36 @@
  * limitations under the License.
  */
 
-package androidx.compose.foundation.lazy
+package androidx.compose.foundation.lazy.layout
 
 import android.view.Choreographer
 import android.view.Display
 import android.view.View
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.RememberObserver
-import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.ui.layout.SubcomposeLayoutState
 import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
 import androidx.compose.ui.layout.SubcomposeMeasureScope
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.trace
 import java.util.concurrent.TimeUnit
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
-internal actual fun LazyListPrefetcher(
-    lazyListState: LazyListState,
-    stateOfItemsProvider: State<LazyListItemsProvider>,
-    itemContentFactory: LazyListItemContentFactory,
+internal actual fun LazyLayoutPrefetcher(
+    prefetchPolicy: LazyLayoutPrefetchPolicy,
+    state: LazyLayoutState,
+    itemContentFactory: LazyLayoutItemContentFactory,
     subcomposeLayoutState: SubcomposeLayoutState
 ) {
     val view = LocalView.current
-    remember(subcomposeLayoutState, lazyListState, view) {
-        LazyListPrefetcher(
+    remember(subcomposeLayoutState, prefetchPolicy, view) {
+        LazyLayoutPrefetcher(
+            prefetchPolicy,
+            state,
             subcomposeLayoutState,
-            lazyListState,
-            stateOfItemsProvider,
             itemContentFactory,
             view
         )
@@ -109,29 +105,24 @@
  *    so critical given that we don't need to calculate the deadline.
  *    Tracking bug: 187393922
  */
-private class LazyListPrefetcher(
+internal class LazyLayoutPrefetcher(
+    private val prefetchPolicy: LazyLayoutPrefetchPolicy,
+    private val state: LazyLayoutState,
     private val subcomposeLayoutState: SubcomposeLayoutState,
-    private val lazyListState: LazyListState,
-    private val stateOfItemsProvider: State<LazyListItemsProvider>,
-    private val itemContentFactory: LazyListItemContentFactory,
+    private val itemContentFactory: LazyLayoutItemContentFactory,
     private val view: View
 ) : RememberObserver,
-    LazyListOnScrolledListener,
-    LazyListOnPostMeasureListener,
+    LazyLayoutOnPostMeasureListener,
+    LazyLayoutPrefetchPolicy.Subscriber,
     Runnable,
     Choreographer.FrameCallback {
 
     /**
-     * Keeps the scrolling direction during the previous calculation in order to be able to
-     * detect the scrolling direction change.
-     */
-    private var wasScrollingForward: Boolean = false
-
-    /**
      * The index scheduled to be prefetched (or the last prefetched index if the prefetch is
-     * done, in this case [precomposedSlotHandle] is not null and associated with this index.
+     * done, in this case [precomposedSlotHandle] is not null and associated with this index).
+     * TODO(popam): probably this should be a queue rather than one element
      */
-    private var indexToPrefetch: Int = -1
+    private var indexToPrefetch = -1
 
     /**
      * Non-null when the item with [indexToPrefetch] index was prefetched.
@@ -175,11 +166,11 @@
                 val beforeNs = System.nanoTime()
                 if (beforeNs > nextFrameNs || beforeNs + averagePrecomposeTimeNs < nextFrameNs) {
                     val index = indexToPrefetch
-                    val itemProvider = stateOfItemsProvider.value
+                    val itemsProvider = state.itemsProvider()
                     if (view.windowVisibility == View.VISIBLE &&
-                        index in 0 until itemProvider.itemsCount
+                        index in 0 until itemsProvider.itemsCount
                     ) {
-                        precomposedSlotHandle = precompose(itemProvider, index)
+                        precomposedSlotHandle = precompose(itemsProvider, index)
                         averagePrecomposeTimeNs = calculateAverageTime(
                             System.nanoTime() - beforeNs,
                             averagePrecomposeTimeNs
@@ -204,7 +195,7 @@
                 if (beforeNs > nextFrameNs || beforeNs + averagePremeasureTimeNs < nextFrameNs) {
                     if (view.windowVisibility == View.VISIBLE) {
                         premeasuringIsNeeded = true
-                        lazyListState.remeasurement.forceRemeasure()
+                        state.remeasure()
                         averagePremeasureTimeNs = calculateAverageTime(
                             System.nanoTime() - beforeNs,
                             averagePremeasureTimeNs
@@ -232,10 +223,10 @@
     }
 
     private fun precompose(
-        itemProvider: LazyListItemsProvider,
+        itemsProvider: LazyLayoutItemsProvider,
         index: Int
     ): PrecomposedSlotHandle {
-        val key = itemProvider.getKey(index)
+        val key = itemsProvider.getKey(index)
         val content = itemContentFactory.getContent(index, key)
         return subcomposeLayoutState.precompose(key, content)
     }
@@ -251,67 +242,40 @@
         }
     }
 
-    /**
-     * The callback to be executed on every scroll.
-     */
-    override fun onScrolled(delta: Float) {
-        if (!lazyListState.prefetchingEnabled) {
-            return
-        }
-        val info = lazyListState.layoutInfo
-        if (info.visibleItemsInfo.isNotEmpty()) {
-            check(isActive)
-            val scrollingForward = delta < 0
-            val indexToPrefetch = if (scrollingForward) {
-                info.visibleItemsInfo.last().index + 1
-            } else {
-                info.visibleItemsInfo.first().index - 1
-            }
-            if (indexToPrefetch != this.indexToPrefetch &&
-                indexToPrefetch in 0 until info.totalItemsCount
-            ) {
-                val precomposedSlot = precomposedSlotHandle
-                if (precomposedSlot != null) {
-                    if (wasScrollingForward != scrollingForward) {
-                        // the scrolling direction has been changed which means the last prefetched
-                        // is not going to be reached anytime soon so it is safer to dispose it.
-                        // if this item is already visible it is safe to call the method anyway
-                        // as it will be no-op
-                        precomposedSlot.dispose()
-                    }
-                }
-                this.wasScrollingForward = scrollingForward
-                this.indexToPrefetch = indexToPrefetch
-                this.precomposedSlotHandle = null
-                premeasuringIsNeeded = false
-                if (!prefetchScheduled) {
-                    prefetchScheduled = true
-                    // schedule the prefetching
-                    view.post(this)
-                }
-            }
+    override fun scheduleForPrefetch(index: Int) {
+        indexToPrefetch = index
+        precomposedSlotHandle = null
+        premeasuringIsNeeded = false
+        if (!prefetchScheduled) {
+            prefetchScheduled = true
+            // schedule the prefetching
+            view.post(this)
         }
     }
 
-    override fun SubcomposeMeasureScope.onPostMeasure(
-        childConstraints: Constraints,
-        result: LazyListMeasureResult
-    ) {
+    override fun removeFromPrefetch(index: Int) {
+        if (index == indexToPrefetch) {
+            precomposedSlotHandle?.dispose()
+            indexToPrefetch = -1
+        }
+    }
+
+    override fun SubcomposeMeasureScope.onPostMeasure(result: LazyLayoutMeasureResult) {
         val index = indexToPrefetch
         if (premeasuringIsNeeded && index != -1) {
             check(isActive)
-            val itemProvider = stateOfItemsProvider.value
-            if (index < itemProvider.itemsCount) {
+            val itemsProvider = state.itemsProvider()
+            if (index < itemsProvider.itemsCount) {
                 val isVisibleAlready = result.visibleItemsInfo.fastAny { it.index == index }
-                val composedButNotVisible = result.composedButNotVisibleItems != null &&
-                    result.composedButNotVisibleItems.fastAny { it.index == index }
+                val composedButNotVisible = result.composedButNotVisibleItemsIndices != null &&
+                    result.composedButNotVisibleItemsIndices!!.fastAny { it == index }
                 if (isVisibleAlready || composedButNotVisible) {
                     premeasuringIsNeeded = false
                 } else {
-                    val key = itemProvider.getKey(index)
+                    val key = itemsProvider.getKey(index)
                     val content = itemContentFactory.getContent(index, key)
                     subcompose(key, content).fastForEach {
-                        it.measure(childConstraints)
+                        it.measure(prefetchPolicy.constraints)
                     }
                 }
             }
@@ -319,15 +283,15 @@
     }
 
     override fun onRemembered() {
-        lazyListState.>
-        lazyListState.>
+        prefetchPolicy.prefetcher = this
+        state.>
         isActive = true
     }
 
     override fun onForgotten() {
         isActive = false
-        lazyListState.>
-        lazyListState.>
+        prefetchPolicy.prefetcher = null
+        state.>
         view.removeCallbacks(this)
         choreographer.removeFrameCallback(this)
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt
index c037ca7..aa3c299 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.lazy
 
 /**
- * Represents an index in the list of items of lazy list.
+ * Represents an index in the list of items of lazy layout.
  */
 @Suppress("NOTHING_TO_INLINE", "INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
 internal inline class DataIndex(val value: Int) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
index f09bc56..eedce14 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/IntervalList.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index 9eea202..8603749 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -22,10 +22,6 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -164,73 +160,6 @@
     itemContent(it, items[it])
 }
 
-private class IntervalContent(
-    val key: ((index: Int) -> Any)?,
-    val content: LazyItemScope.(index: Int) -> @Composable() () -> Unit
-)
-
-private class LazyListScopeImpl : LazyListScope, LazyListItemsProvider {
-    private val intervals = IntervalList<IntervalContent>()
-    override val itemsCount get() = intervals.totalSize
-    private var _headerIndexes: MutableList<Int>? = null
-    override val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()
-
-    override fun getKey(index: Int): Any {
-        val interval = intervals.intervalForIndex(index)
-        val localIntervalIndex = index - interval.startIndex
-        val key = interval.content.key?.invoke(localIntervalIndex)
-        return key ?: getDefaultLazyKeyFor(index)
-    }
-
-    override fun getContent(index: Int, scope: LazyItemScope): @Composable () -> Unit {
-        val interval = intervals.intervalForIndex(index)
-        val localIntervalIndex = index - interval.startIndex
-        return interval.content.content.invoke(scope, localIntervalIndex)
-    }
-
-    override fun items(
-        count: Int,
-        key: ((index: Int) -> Any)?,
-        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
-    ) {
-        intervals.add(
-            count,
-            IntervalContent(
-                key = key,
-                content = { index -> @Composable { itemContent(index) } }
-            )
-        )
-    }
-
-    override fun item(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
-        intervals.add(
-            1,
-            IntervalContent(
-                key = if (key != null) { _: Int -> key } else null,
-                content = { @Composable { content() } }
-            )
-        )
-    }
-
-    @ExperimentalFoundationApi
-    override fun stickyHeader(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
-        val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {
-            _headerIndexes = it
-        }
-        headersIndexes.add(itemsCount)
-
-        item(key, content)
-    }
-}
-
-/**
- * This should create an object meeting following requirements:
- * 1) objects created for the same index are equals and never equals for different indexes
- * 2) this class is saveable via a default SaveableStateRegistry on the platform
- * 3) this objects can't be equals to any object which could be provided by a user as a custom key
- */
-internal expect fun getDefaultLazyKeyFor(index: Int): Any
-
 /**
  * The horizontally scrolling list that only composes and lays out the currently visible items.
  * The [content] block defines a DSL which allows you to emit items of different types. For
@@ -269,7 +198,6 @@
     content: LazyListScope.() -> Unit
 ) {
     LazyList(
-        stateOfItemsProvider = rememberStateOfItemsProvider(content),
         modifier = modifier,
         state = state,
         contentPadding = contentPadding,
@@ -277,7 +205,8 @@
         horizontalArrangement = horizontalArrangement,
         isVertical = false,
         flingBehavior = flingBehavior,
-        reverseLayout = reverseLayout
+        reverseLayout = reverseLayout,
+        content = content
     )
 }
 
@@ -319,7 +248,6 @@
     content: LazyListScope.() -> Unit
 ) {
     LazyList(
-        stateOfItemsProvider = rememberStateOfItemsProvider(content),
         modifier = modifier,
         state = state,
         contentPadding = contentPadding,
@@ -327,16 +255,15 @@
         horizontalAlignment = horizontalAlignment,
         verticalArrangement = verticalArrangement,
         isVertical = true,
-        reverseLayout = reverseLayout
+        reverseLayout = reverseLayout,
+        content = content
     )
 }
 
-@Composable
-private fun rememberStateOfItemsProvider(
-    content: LazyListScope.() -> Unit
-): State<LazyListItemsProvider> {
-    val latestContent = rememberUpdatedState(content)
-    return remember {
-        derivedStateOf { LazyListScopeImpl().apply(latestContent.value) }
-    }
-}
+/**
+ * This should create an object meeting following requirements:
+ * 1) objects created for the same index are equals and never equals for different indexes
+ * 2) this class is saveable via a default SaveableStateRegistry on the platform
+ * 3) this objects can't be equals to any object which could be provided by a user as a custom key
+ */
+internal expect fun getDefaultLazyKeyFor(index: Int): Any
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
index 9ca44a3..4874764 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
@@ -242,9 +242,10 @@
         intervals.add(1) { @Composable { content() } }
     }
 
-    override fun items(count: Int, itemContent: @Composable LazyItemScope.(index: Int) -> Unit) {
-        intervals.add(count) {
-            @Composable { itemContent(it) }
-        }
+    override fun items(
+        count: Int,
+        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
+    ) {
+        intervals.add(count) { @Composable { itemContent(it) } }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
index 39ce194..17fd69a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
@@ -16,9 +16,14 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
 
 /**
  * Receiver scope being used by the item content parameter of LazyColumn/Row.
@@ -28,8 +33,8 @@
 interface LazyItemScope {
     /**
      * Have the content fill the [Constraints.maxWidth] and [Constraints.maxHeight] of the parent
-     * measurement constraints by setting the [minimum width][Constraints.minWidth] to be equal to the
-     * [maximum width][Constraints.maxWidth] multiplied by [fraction] and the [minimum
+     * measurement constraints by setting the [minimum width][Constraints.minWidth] to be equal to
+     * the [maximum width][Constraints.maxWidth] multiplied by [fraction] and the [minimum
      * height][Constraints.minHeight] to be equal to the [maximum height][Constraints.maxHeight]
      * multiplied by [fraction]. Note that, by default, the [fraction] is 1, so the modifier will
      * make the content fill the whole available space. [fraction] must be between `0` and `1`.
@@ -72,3 +77,22 @@
         fraction: Float = 1f
     ): Modifier
 }
+
+internal data class LazyItemScopeImpl(
+    val density: Density,
+    val constraints: Constraints
+) : LazyItemScope {
+    private val maxWidth: Dp = with(density) { constraints.maxWidth.toDp() }
+    private val maxHeight: Dp = with(density) { constraints.maxHeight.toDp() }
+
+    override fun Modifier.fillParentMaxSize(fraction: Float) = size(
+        maxWidth * fraction,
+        maxHeight * fraction
+    )
+
+    override fun Modifier.fillParentMaxWidth(fraction: Float) =
+        width(maxWidth * fraction)
+
+    override fun Modifier.fillParentMaxHeight(fraction: Float) =
+        height(maxHeight * fraction)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 92c623b..c54624d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.assertNotNestingScrollableContainers
 import androidx.compose.foundation.clipScrollableContainer
 import androidx.compose.foundation.gestures.FlingBehavior
@@ -28,24 +29,28 @@
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.layout.LazyLayout
+import androidx.compose.foundation.lazy.layout.LazyMeasurePolicy
+import androidx.compose.foundation.lazy.layout.rememberLazyLayoutPrefetchPolicy
+import androidx.compose.foundation.lazy.layout.rememberLazyLayoutState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.layout.IntrinsicMeasureScope
-import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.layout.SubcomposeLayoutState
+import androidx.compose.ui.node.Ref
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 
 @Composable
 internal fun LazyList(
-    /** State object containing the latest item provider */
-    stateOfItemsProvider: State<LazyListItemsProvider>,
     /** Modifier to be applied for the inner layout */
     modifier: Modifier,
     /** State controlling the scroll position */
@@ -65,23 +70,41 @@
     /** The alignment to align items vertically. Required when isVertical is false */
     verticalAlignment: Alignment.Vertical? = null,
     /** The horizontal arrangement for items. Required when isVertical is false */
-    horizontalArrangement: Arrangement.Horizontal? = null
+    horizontalArrangement: Arrangement.Horizontal? = null,
+    /** The content of the list */
+    content: LazyListScope.() -> Unit
 ) {
-    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    // reverse scroll by default, to have "natural" gesture that goes reversed to layout
-    // if rtl and horizontal, do not reverse to make it right-to-left
-    val reverseScrollDirection = if (!isVertical && isRtl) reverseLayout else !reverseLayout
-
-    val itemContentFactory = rememberItemContentFactory(stateOfItemsProvider, state)
-
-    val subcomposeLayoutState = remember { SubcomposeLayoutState(MaxItemsToRetainForReuse) }
-    LazyListPrefetcher(state, stateOfItemsProvider, itemContentFactory, subcomposeLayoutState)
-
     val overScrollController = rememberOverScrollController()
 
-    SubcomposeLayout(
-        subcomposeLayoutState,
-        modifier
+    val itemScope: Ref<LazyItemScopeImpl> = remember { Ref() }
+
+    val stateOfItemsProvider = rememberStateOfItemsProvider(content, itemScope)
+
+    val measurePolicy = rememberLazyListMeasurePolicy(
+        stateOfItemsProvider,
+        itemScope,
+        state,
+        overScrollController,
+        contentPadding,
+        reverseLayout,
+        isVertical,
+        horizontalAlignment,
+        verticalArrangement,
+        verticalAlignment,
+        horizontalArrangement
+    )
+
+    state.prefetchPolicy = rememberLazyLayoutPrefetchPolicy()
+    state.innerState = rememberLazyLayoutState()
+
+    val itemsProvider = stateOfItemsProvider.value
+    if (itemsProvider.itemsCount > 0) {
+        state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
+    }
+
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    LazyLayout(
+        modifier = modifier
             .lazyListSemantics(
                 stateOfItemsProvider = stateOfItemsProvider,
                 state = state,
@@ -91,25 +114,128 @@
             .clipScrollableContainer(isVertical)
             .scrollable(
                 orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-                reverseDirection = reverseScrollDirection,
+                reverseDirection = if (!isVertical && isRtl) reverseLayout else !reverseLayout,
                 interactionSource = state.internalInteractionSource,
                 flingBehavior = flingBehavior,
                 state = state,
                 overScrollController = overScrollController
             )
-            .padding(contentPadding)
-            .then(state.remeasurementModifier)
-    ) { constraints ->
+            .padding(contentPadding),
+        state = state.innerState,
+        prefetchPolicy = state.prefetchPolicy,
+        measurePolicy = measurePolicy,
+        itemsProvider = { stateOfItemsProvider.value }
+    )
+}
+
+@Composable
+private fun rememberStateOfItemsProvider(
+    content: LazyListScope.() -> Unit,
+    itemScope: Ref<LazyItemScopeImpl>
+): State<LazyListItemsProvider> {
+    val latestContent = rememberUpdatedState(content)
+    return remember {
+        derivedStateOf { LazyListScopeImpl(itemScope).apply(latestContent.value) }
+    }
+}
+
+internal class LazyListScopeImpl(
+    private val itemScope: Ref<LazyItemScopeImpl>
+) : LazyListScope, LazyListItemsProvider {
+    private val intervals = IntervalList<IntervalContent>()
+    override val itemsCount get() = intervals.totalSize
+    private var _headerIndexes: MutableList<Int>? = null
+    override val headerIndexes: List<Int> get() = _headerIndexes ?: emptyList()
+
+    override fun getKey(index: Int): Any {
+        val interval = intervals.intervalForIndex(index)
+        val localIntervalIndex = index - interval.startIndex
+        val key = interval.content.key?.invoke(localIntervalIndex)
+        return key ?: getDefaultLazyKeyFor(index)
+    }
+
+    override fun getContent(index: Int): @Composable () -> Unit {
+        val interval = intervals.intervalForIndex(index)
+        val localIntervalIndex = index - interval.startIndex
+        return interval.content.content.invoke(itemScope.value!!, localIntervalIndex)
+    }
+
+    override fun items(
+        count: Int,
+        key: ((index: Int) -> Any)?,
+        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
+    ) {
+        intervals.add(
+            count,
+            IntervalContent(
+                key = key,
+                content = { index -> @Composable { itemContent(index) } }
+            )
+        )
+    }
+
+    override fun item(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
+        intervals.add(
+            1,
+            IntervalContent(
+                key = if (key != null) { _: Int -> key } else null,
+                content = { @Composable { content() } }
+            )
+        )
+    }
+
+    @ExperimentalFoundationApi
+    override fun stickyHeader(key: Any?, content: @Composable LazyItemScope.() -> Unit) {
+        val headersIndexes = _headerIndexes ?: mutableListOf<Int>().also {
+            _headerIndexes = it
+        }
+        headersIndexes.add(itemsCount)
+
+        item(key, content)
+    }
+}
+
+internal class IntervalContent(
+    val key: ((index: Int) -> Any)?,
+    val content: LazyItemScope.(index: Int) -> @Composable () -> Unit
+)
+
+@Composable
+private fun rememberLazyListMeasurePolicy(
+    /** State containing the items provider of the list. */
+    stateOfItemsProvider: State<LazyListItemsProvider>,
+    /** Value holder for the item scope used to compose items. */
+    itemScope: Ref<LazyItemScopeImpl>,
+    /** The state of the list. */
+    state: LazyListState,
+    /** The overscroll controller. */
+    overScrollController: OverScrollController,
+    /** The inner padding to be added for the whole content(nor for each individual item) */
+    contentPadding: PaddingValues,
+    /** reverse the direction of scrolling and layout */
+    reverseLayout: Boolean,
+    /** The layout orientation of the list */
+    isVertical: Boolean,
+    /** The alignment to align items horizontally. Required when isVertical is true */
+    horizontalAlignment: Alignment.Horizontal? = null,
+    /** The vertical arrangement for items. Required when isVertical is true */
+    verticalArrangement: Arrangement.Vertical? = null,
+    /** The alignment to align items vertically. Required when isVertical is false */
+    verticalAlignment: Alignment.Vertical? = null,
+    /** The horizontal arrangement for items. Required when isVertical is false */
+    horizontalArrangement: Arrangement.Horizontal? = null
+) = remember {
+    LazyMeasurePolicy { measurables, constraints ->
         constraints.assertNotNestingScrollableContainers(isVertical)
 
         val itemsProvider = stateOfItemsProvider.value
         state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
 
         // Update the state's cached Density
-        state.density = Density(density, fontScale)
+        state.density = this
 
         // this will update the scope object if the constrains have been changed
-        itemContentFactory.updateItemScope(this, constraints)
+        itemScope.update(this, constraints)
 
         val startContentPadding = if (isVertical) {
             contentPadding.calculateTopPadding()
@@ -134,9 +260,8 @@
         val itemProvider = LazyMeasuredItemProvider(
             constraints,
             isVertical,
-            this,
             itemsProvider,
-            itemContentFactory
+            measurables
         ) { index, key, placeables ->
             // we add spaceBetweenItems as an extra spacing for all items apart from the last one so
             // the lazy list measuring logic will take it into account.
@@ -155,8 +280,9 @@
                 key = key
             )
         }
+        state.prefetchPolicy?.constraints = itemProvider.childConstraints
 
-        val measureResult = measureLazyList(
+        measureLazyList(
             itemsCount = itemsCount,
             itemProvider = itemProvider,
             mainAxisMaxSize = mainAxisMaxSize,
@@ -172,38 +298,21 @@
             horizontalArrangement = horizontalArrangement,
             reverseLayout = reverseLayout,
             density = this,
-            layoutDirection = layoutDirection
-        )
-
-        state.applyMeasureResult(measureResult)
-
-        refreshOverScrollInfo(overScrollController, measureResult, contentPadding)
-
-        state.onPostMeasureListener?.apply {
-            onPostMeasure(itemProvider.childConstraints, measureResult)
-        }
-
-        layout(
-            width = measureResult.layoutWidth,
-            height = measureResult.layoutHeight,
-            placementBlock = measureResult.placementBlock
-        )
+            layoutDirection = layoutDirection,
+            layout = { width, height, placement -> layout(width, height, emptyMap(), placement) }
+        ).also {
+            state.applyMeasureResult(it)
+            refreshOverScrollInfo(overScrollController, it, contentPadding)
+        }.lazyLayoutMeasureResult
     }
 }
 
-private const val MaxItemsToRetainForReuse = 2
-
-/**
- * Platform specific implementation of lazy list prefetching - precomposing next items in
- * advance during the scrolling.
- */
-@Composable
-internal expect fun LazyListPrefetcher(
-    lazyListState: LazyListState,
-    stateOfItemsProvider: State<LazyListItemsProvider>,
-    itemContentFactory: LazyListItemContentFactory,
-    subcomposeLayoutState: SubcomposeLayoutState
-)
+private fun Ref<LazyItemScopeImpl>.update(density: Density, constraints: Constraints) {
+    val value = value
+    if (value == null || value.density != density || value.constraints != constraints) {
+        this.value = LazyItemScopeImpl(density, constraints)
+    }
+}
 
 private fun IntrinsicMeasureScope.refreshOverScrollInfo(
     overScrollController: OverScrollController,
@@ -224,8 +333,8 @@
 
     overScrollController.refreshContainerInfo(
         Size(
-            result.layoutWidth.toFloat() + horizontalPadding.roundToPx(),
-            result.layoutHeight.toFloat() + verticalPadding.roundToPx()
+            result.width.toFloat() + horizontalPadding.roundToPx(),
+            result.height.toFloat() + verticalPadding.roundToPx()
         ),
         canScrollForward || canScrollBackward
     )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
deleted file mode 100644
index 38d63f0..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.lazy
-
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.SaveableStateHolder
-import androidx.compose.runtime.saveable.rememberSaveableStateHolder
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-
-@Composable
-internal fun rememberItemContentFactory(
-    stateOfItemsProvider: State<LazyListItemsProvider>,
-    state: LazyListState
-): LazyListItemContentFactory {
-    val saveableStateHolder = rememberSaveableStateHolder()
-    val factory = remember(stateOfItemsProvider) {
-        LazyListItemContentFactory(saveableStateHolder, stateOfItemsProvider)
-    }
-    factory.updateKeyIndexMappingForVisibleItems(state)
-    return factory
-}
-
-/**
- * This class:
- * 1) Caches the lambdas being produced by [itemsProvider]. This allows us to perform less
- * recompositions as the compose runtime can skip the whole composition if we subcompose with the
- * same instance of the content lambda.
- * 2) Updates the mapping between keys and indexes when we have a new factory
- * 3) Creates an [itemScope] to be used with [itemsProvider]
- * 4) Adds state restoration on top of the composable returned by [itemsProvider] with help of
- * [saveableStateHolder].
- */
-internal class LazyListItemContentFactory(
-    private val saveableStateHolder: SaveableStateHolder,
-    private var itemsProvider: State<LazyListItemsProvider>,
-) {
-    /**
-     * Contains the cached lambdas produced by the [itemsProvider].
-     */
-    private val lambdasCache = mutableMapOf<Any, CachedItemContent>()
-
-    /**
-     * We iterate through the currently composed keys and update the associated indexes so we can
-     * smartly handle reorderings. If we will not do it and just wait for the next remeasure the
-     * item could be recomposed before it and switch to start displaying the wrong item.
-     */
-    fun updateKeyIndexMappingForVisibleItems(state: LazyListState) {
-        val itemsProvider = itemsProvider.value
-        val itemsCount = itemsProvider.itemsCount
-        if (itemsCount > 0) {
-            state.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
-            val firstVisible = state.firstVisibleItemIndexNonObservable.value
-            val count = state.visibleItemsCount
-            for (i in firstVisible until minOf(itemsCount, firstVisible + count)) {
-                val key = itemsProvider.getKey(i)
-                lambdasCache[key]?.index = i
-            }
-        }
-    }
-
-    /**
-     * Return cached item content lambda or creates a new lambda and puts it in the cache.
-     */
-    fun getContent(index: Int, key: Any): @Composable () -> Unit {
-        val cachedContent = lambdasCache[key]
-        return if (cachedContent != null && cachedContent.index == index) {
-            cachedContent.content
-        } else {
-            val newContent = CachedItemContent(index, itemScope, key)
-            lambdasCache[key] = newContent
-            newContent.content
-        }
-    }
-
-    private inner class CachedItemContent(
-        initialIndex: Int,
-        private val scope: LazyItemScopeImpl,
-        val key: Any
-    ) {
-        var index by mutableStateOf(initialIndex)
-
-        val content: @Composable () -> Unit = @Composable {
-            val itemsProvider = itemsProvider.value
-            if (index < itemsProvider.itemsCount) {
-                val key = itemsProvider.getKey(index)
-                if (key == this.key) {
-                    val content = itemsProvider.getContent(index, scope)
-                    saveableStateHolder.SaveableStateProvider(key, content)
-                }
-            }
-        }
-    }
-
-    /**
-     * The cached instance of the scope to be used for composing items.
-     */
-    private var itemScope = InitialLazyItemsScopeImpl
-
-    /**
-     * Updates the [itemScope] with the last [constraints] we got from the parent.
-     */
-    fun updateItemScope(density: Density, constraints: Constraints) {
-        if (itemScope.density != density || itemScope.constraints != constraints) {
-            itemScope = LazyItemScopeImpl(density, constraints)
-            lambdasCache.clear()
-        }
-    }
-}
-
-/**
- * Pre-allocated initial value for [LazyItemScopeImpl] to not have it nullable and avoid using
- * late init.
- */
-private val InitialLazyItemsScopeImpl = LazyItemScopeImpl(Density(0f, 0f), Constraints())
-
-private data class LazyItemScopeImpl(
-    val density: Density,
-    val constraints: Constraints
-) : LazyItemScope {
-    private val maxWidth: Dp = with(density) { constraints.maxWidth.toDp() }
-    private val maxHeight: Dp = with(density) { constraints.maxHeight.toDp() }
-
-    override fun Modifier.fillParentMaxSize(fraction: Float) = size(
-        maxWidth * fraction,
-        maxHeight * fraction
-    )
-
-    override fun Modifier.fillParentMaxWidth(fraction: Float) =
-        width(maxWidth * fraction)
-
-    override fun Modifier.fillParentMaxHeight(fraction: Float) =
-        height(maxHeight * fraction)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
index 20afb64..d5e4c5d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
@@ -17,7 +17,8 @@
 package androidx.compose.foundation.lazy
 
 /**
- * Contains useful information about an individual item in lazy lists like [LazyColumn] or [LazyRow].
+ * Contains useful information about an individual item in lazy lists like [LazyColumn]
+ *  or [LazyRow].
  *
  * @see LazyListLayoutInfo
  */
@@ -28,6 +29,11 @@
     val index: Int
 
     /**
+     * The key of the item which was passed to the item() or items() function.
+     */
+    val key: Any
+
+    /**
      * The main axis offset of the item. It is relative to the start of the lazy list container.
      */
     val offset: Int
@@ -37,9 +43,4 @@
      * slot for the item then this size will be calculated as the sum of their sizes.
      */
     val size: Int
-
-    /**
-     * The key of the item which was passed to the item() or items() function.
-     */
-    val key: Any
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProvider.kt
index 85cf980..d721bff 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemsProvider.kt
@@ -16,18 +16,9 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.runtime.Composable
+import androidx.compose.foundation.lazy.layout.LazyLayoutItemsProvider
 
-internal interface LazyListItemsProvider {
-    /** The total size of the list */
-    val itemsCount: Int
-
+internal interface LazyListItemsProvider : LazyLayoutItemsProvider {
     /** The list of indexes of the sticky header items */
     val headerIndexes: List<Int>
-
-    /** Returns the key for the item on this index */
-    fun getKey(index: Int): Any
-
-    /** Returns the content lambda for the given index and scope object */
-    fun getContent(index: Int, scope: LazyItemScope): @Composable() () -> Unit
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 6eb219c..e121f8b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -17,6 +17,8 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
@@ -47,7 +49,8 @@
     horizontalArrangement: Arrangement.Horizontal?,
     reverseLayout: Boolean,
     density: Density,
-    layoutDirection: LayoutDirection
+    layoutDirection: LayoutDirection,
+    layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyListMeasureResult {
     require(startContentPadding >= 0)
     require(endContentPadding >= 0)
@@ -59,9 +62,7 @@
             canScrollForward = false,
             consumedScroll = 0f,
             composedButNotVisibleItems = null,
-            layoutWidth = constraints.minWidth,
-            layoutHeight = constraints.minHeight,
-            placementBlock = {},
+            measureResult = layout(constraints.minWidth, constraints.minHeight) {},
             visibleItemsInfo = emptyList(),
             viewportStartOffset = -startContentPadding,
             viewportEndOffset = endContentPadding,
@@ -258,9 +259,7 @@
             canScrollForward = mainAxisUsed > maxOffset,
             consumedScroll = consumedScroll,
             composedButNotVisibleItems = notUsedButComposedItems,
-            layoutWidth = layoutWidth,
-            layoutHeight = layoutHeight,
-            placementBlock = {
+            measureResult = layout(layoutWidth, layoutHeight) {
                 visibleItems.fastForEach {
                     if (it !== headerItem) {
                         it.place(this, layoutWidth, layoutHeight)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
index 773c0f7..9378177 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
@@ -16,7 +16,10 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.ui.layout.Placeable
+import androidx.compose.foundation.lazy.layout.LazyLayoutItemInfo
+import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureResult
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.util.fastMap
 
 /**
  * The result of the measure pass for lazy list layout.
@@ -33,13 +36,8 @@
     val consumedScroll: Float,
     /** List of items which were composed, but are not a part of [visibleItemsInfo].*/
     val composedButNotVisibleItems: List<LazyMeasuredItem>?,
-    // properties to be used by the Layout's measure result
-    /** The calculated layout width */
-    val layoutWidth: Int,
-    /** The calculated layout height */
-    val layoutHeight: Int,
-    /** The placement block */
-    val placementBlock: Placeable.PlacementScope.() -> Unit,
+    // MeasureResult defining the layout
+    val measureResult: MeasureResult,
     // properties representing the info needed for LazyListLayoutInfo
     /** see [LazyListLayoutInfo.visibleItemsInfo] */
     override val visibleItemsInfo: List<LazyListItemInfo>,
@@ -49,4 +47,17 @@
     override val viewportEndOffset: Int,
     /** see [LazyListLayoutInfo.totalItemsCount] */
     override val totalItemsCount: Int,
-) : LazyListLayoutInfo
+) : LazyListLayoutInfo, MeasureResult by measureResult {
+    val lazyLayoutMeasureResult: LazyLayoutMeasureResult get() =
+        object : LazyLayoutMeasureResult, MeasureResult by measureResult {
+            override val visibleItemsInfo: List<LazyLayoutItemInfo>
+                get() = this@LazyListMeasureResult.visibleItemsInfo.fastMap {
+                    object : LazyLayoutItemInfo {
+                        override val index: Int get() = it.index
+                        override val key: Any get() = it.key
+                    }
+                }
+            override val composedButNotVisibleItemsIndices: List<Int>?
+                get() = composedButNotVisibleItems?.fastMap { it.index }
+        }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 51f0da8..c019af8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -22,15 +22,14 @@
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchPolicy
+import androidx.compose.foundation.lazy.layout.LazyLayoutState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.layout.Remeasurement
-import androidx.compose.ui.layout.RemeasurementModifier
-import androidx.compose.ui.layout.SubcomposeMeasureScope
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import kotlin.math.abs
@@ -59,7 +58,7 @@
 }
 
 /**
- * A state object that can be hoisted to control and observe scrolling
+ * A state object that can be hoisted to control and observe scrolling.
  *
  * In most cases, this will be created via [rememberLazyListState].
  *
@@ -141,12 +140,6 @@
     private val scrollableState = ScrollableState { -onScroll(-it) }
 
     /**
-     * The [Remeasurement] object associated with our layout. It allows us to remeasure
-     * synchronously during scroll.
-     */
-    internal lateinit var remeasurement: Remeasurement
-
-    /**
      * Only used for testing to confirm that we're not making too many measure passes
      */
     /*@VisibleForTesting*/
@@ -160,13 +153,20 @@
     internal var prefetchingEnabled: Boolean = true
 
     /**
-     * The modifier which provides [remeasurement].
+     * The index scheduled to be prefetched (or the last prefetched index if the prefetch is done).
      */
-    internal val remeasurementModifier = object : RemeasurementModifier {
-        override fun onRemeasurementAvailable(remeasurement: Remeasurement) {
-            this@LazyListState.remeasurement = remeasurement
-        }
-    }
+    private var indexToPrefetch = -1
+
+    /**
+     * Keeps the scrolling direction during the previous calculation in order to be able to
+     * detect the scrolling direction change.
+     */
+    private var wasScrollingForward = false
+
+    /**
+     * The state of the inner LazyLayout.
+     */
+    internal lateinit var innerState: LazyLayoutState
 
     /**
      * Instantly brings the item at [index] to the top of the viewport, offset by [scrollOffset]
@@ -185,13 +185,15 @@
         index: Int,
         /*@IntRange(from = 0)*/
         scrollOffset: Int = 0
-    ) = scrollableState.scroll {
-        snapToItemIndexInternal(index, scrollOffset)
+    ) {
+        return scrollableState.scroll {
+            snapToItemIndexInternal(index, scrollOffset)
+        }
     }
 
     internal fun snapToItemIndexInternal(index: Int, scrollOffset: Int) {
         scrollPosition.requestPosition(DataIndex(index), scrollOffset)
-        remeasurement.forceRemeasure()
+        innerState.remeasure()
     }
 
     /**
@@ -217,9 +219,6 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
-    internal var onScrolledListener: LazyListOnScrolledListener? = null
-    internal var onPostMeasureListener: LazyListOnPostMeasureListener? = null
-
     private var canScrollBackward: Boolean = false
     private var canScrollForward: Boolean = false
 
@@ -240,8 +239,10 @@
         // we have less than 0.5 pixels
         if (abs(scrollToBeConsumed) > 0.5f) {
             val preScrollToBeConsumed = scrollToBeConsumed
-            remeasurement.forceRemeasure()
-            onScrolledListener?.onScrolled(preScrollToBeConsumed - scrollToBeConsumed)
+            innerState.remeasure()
+            if (prefetchingEnabled && prefetchPolicy != null) {
+                notifyPrefetch(preScrollToBeConsumed - scrollToBeConsumed)
+            }
         }
 
         // here scrollToBeConsumed is already consumed during the forceRemeasure invocation
@@ -258,6 +259,38 @@
         }
     }
 
+    private fun notifyPrefetch(delta: Float) {
+        if (!prefetchingEnabled) {
+            return
+        }
+        val info = layoutInfo
+        if (info.visibleItemsInfo.isNotEmpty()) {
+            // check(isActive)
+            val scrollingForward = delta < 0
+            val indexToPrefetch = if (scrollingForward) {
+                info.visibleItemsInfo.last().index + 1
+            } else {
+                info.visibleItemsInfo.first().index - 1
+            }
+            if (indexToPrefetch != this.indexToPrefetch &&
+                indexToPrefetch in 0 until info.totalItemsCount
+            ) {
+                if (wasScrollingForward != scrollingForward) {
+                    // the scrolling direction has been changed which means the last prefetched
+                    // is not going to be reached anytime soon so it is safer to dispose it.
+                    // if this item is already visible it is safe to call the method anyway
+                    // as it will be no-op
+                    prefetchPolicy?.removeFromPrefetch(this.indexToPrefetch)
+                }
+                this.wasScrollingForward = scrollingForward
+                this.indexToPrefetch = indexToPrefetch
+                prefetchPolicy?.scheduleForPrefetch(indexToPrefetch)
+            }
+        }
+    }
+
+    internal var prefetchPolicy: LazyLayoutPrefetchPolicy? = null
+
     /**
      * Animate (smooth scroll) to the given item.
      *
@@ -314,6 +347,12 @@
                 )
             }
         )
+
+        /**
+         * Pre-allocated initial value for [LazyItemScopeImpl] to not have it nullable and
+         * avoid using late init.
+         */
+        private val InitialLazyItemsScopeImpl = LazyItemScopeImpl(Density(0f, 0f), Constraints())
     }
 }
 
@@ -323,14 +362,3 @@
     override val viewportEndOffset = 0
     override val totalItemsCount = 0
 }
-
-internal interface LazyListOnScrolledListener {
-    fun onScrolled(delta: Float)
-}
-
-internal interface LazyListOnPostMeasureListener {
-    fun SubcomposeMeasureScope.onPostMeasure(
-        childConstraints: Constraints,
-        result: LazyListMeasureResult
-    )
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
index 8d4d0dd..ebecce6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.lazy.layout.LazyMeasurablesProvider
 import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.SubcomposeMeasureScope
 import androidx.compose.ui.unit.Constraints
 
 /**
@@ -26,9 +26,8 @@
 internal class LazyMeasuredItemProvider(
     constraints: Constraints,
     isVertical: Boolean,
-    private val scope: SubcomposeMeasureScope,
     private val itemsProvider: LazyListItemsProvider,
-    private val itemContentFactory: LazyListItemContentFactory,
+    private val measurables: LazyMeasurablesProvider,
     private val measuredItemFactory: MeasuredItemFactory
 ) {
     // the constraints we will measure child with. the main axis is not restricted
@@ -44,8 +43,7 @@
      */
     fun getAndMeasure(index: DataIndex): LazyMeasuredItem {
         val key = itemsProvider.getKey(index.value)
-        val content = itemContentFactory.getContent(index.value, key)
-        val measurables = scope.subcompose(key, content)
+        val measurables = measurables[index.value]
         val placeables = Array(measurables.size) {
             measurables[it].measure(childConstraints)
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazySemantics.kt
index a0606a7..f5ef66d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazySemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazySemantics.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.gestures.animateScrollBy
-import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.runtime.State
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.CollectionInfo
@@ -76,9 +75,9 @@
         }
 
         scrollToIndex { index ->
-            require(index >= 0 && index < stateOfItemsProvider.value.itemsCount) {
-                "Can't scroll to index $index, it is out of bounds [0, ${stateOfItemsProvider
-                    .value.itemsCount})"
+            require(index >= 0 && index < state.layoutInfo.totalItemsCount) {
+                "Can't scroll to index $index, it is out of " +
+                    "bounds [0, ${state.layoutInfo.totalItemsCount})"
             }
             coroutineScope.launch {
                 state.scrollToItem(index)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
new file mode 100644
index 0000000..0eb750b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.SubcomposeLayoutState
+
+/**
+ * A layout that only composes and lays out currently visible items. Can be used to build
+ * efficient scrollable layouts.
+ */
+@Composable
+internal fun LazyLayout(
+    itemsProvider: () -> LazyLayoutItemsProvider,
+    modifier: Modifier = Modifier,
+    state: LazyLayoutState = rememberLazyLayoutState(),
+    prefetchPolicy: LazyLayoutPrefetchPolicy? = null,
+    measurePolicy: LazyMeasurePolicy
+) {
+    state.itemsProvider = itemsProvider
+    val itemContentFactory = rememberItemContentFactory(state)
+    val subcomposeLayoutState = remember { SubcomposeLayoutState(MaxItemsToRetainForReuse) }
+    prefetchPolicy?.let {
+        LazyLayoutPrefetcher(prefetchPolicy, state, itemContentFactory, subcomposeLayoutState)
+    }
+
+    SubcomposeLayout(
+        subcomposeLayoutState,
+        modifier.then(state.remeasurementModifier)
+    ) { constraints ->
+        itemContentFactory.onBeforeMeasure(this, constraints)
+
+        val measurables = LazyMeasurablesProvider(
+            state.itemsProvider(),
+            itemContentFactory,
+            this
+        )
+        val measureResult = with(measurePolicy) { measure(measurables, constraints) }
+
+        state.onPostMeasureListener?.apply { onPostMeasure(measureResult) }
+        state.layoutInfoState.value = measureResult
+        state.layoutInfoNonObservable = measureResult
+
+        measureResult
+    }
+}
+
+private const val MaxItemsToRetainForReuse = 2
+
+/**
+ * Platform specific implementation of lazy layout items prefetching - precomposing next items in
+ * advance during the scrolling.
+ */
+@Composable
+internal expect fun LazyLayoutPrefetcher(
+    prefetchPolicy: LazyLayoutPrefetchPolicy,
+    state: LazyLayoutState,
+    itemContentFactory: LazyLayoutItemContentFactory,
+    subcomposeLayoutState: SubcomposeLayoutState
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
new file mode 100644
index 0000000..2e23171
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.SaveableStateHolder
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.fastForEach
+
+@Composable
+internal fun rememberItemContentFactory(state: LazyLayoutState): LazyLayoutItemContentFactory {
+    val saveableStateHolder = rememberSaveableStateHolder()
+    val itemsProvider = state.itemsProvider
+    val factory = remember(itemsProvider) {
+        LazyLayoutItemContentFactory(saveableStateHolder, itemsProvider)
+    }
+    factory.updateKeyIndexMappingForVisibleItems(state)
+    return factory
+}
+
+/**
+ * This class:
+ * 1) Caches the lambdas being produced by [itemsProvider]. This allows us to perform less
+ * recompositions as the compose runtime can skip the whole composition if we subcompose with the
+ * same instance of the content lambda.
+ * 2) Updates the mapping between keys and indexes when we have a new factory
+ * 3) Adds state restoration on top of the composable returned by [itemsProvider] with help of
+ * [saveableStateHolder].
+ */
+internal class LazyLayoutItemContentFactory(
+    private val saveableStateHolder: SaveableStateHolder,
+    private val itemsProvider: () -> LazyLayoutItemsProvider,
+) {
+    /** Contains the cached lambdas produced by the [itemsProvider]. */
+    private val lambdasCache = mutableMapOf<Any, CachedItemContent>()
+
+    /** Density used to obtain the cached lambdas. */
+    private var densityOfCachedLambdas = Density(0f, 0f)
+
+    /** Constraints used to obtain the cached lambdas. */
+    private var constraintsOfCachedLambdas = Constraints()
+
+    /**
+     * We iterate through the currently composed keys and update the associated indexes so we can
+     * smartly handle reorderings. If we will not do it and just wait for the next remeasure the
+     * item could be recomposed before it and switch to start displaying the wrong item.
+     */
+    fun updateKeyIndexMappingForVisibleItems(state: LazyLayoutState) {
+        val itemsProvider = itemsProvider()
+        val itemsCount = itemsProvider.itemsCount
+        if (itemsCount > 0) {
+            state.layoutInfoNonObservable.visibleItemsInfo.fastForEach {
+                if (it.index < itemsCount) {
+                    val key = itemsProvider.getKey(it.index)
+                    lambdasCache[key]?.index = it.index
+                }
+            }
+        }
+    }
+
+    /**
+     * Invalidate the cached lambas if the density or constraints have changed.
+     * TODO(popam): probably LazyLayoutState should provide an invalidate() method instead.
+     */
+    fun onBeforeMeasure(density: Density, constraints: Constraints) {
+        if (density != densityOfCachedLambdas || constraints != constraintsOfCachedLambdas) {
+            densityOfCachedLambdas = density
+            constraintsOfCachedLambdas = constraints
+            lambdasCache.clear()
+        }
+    }
+
+    /**
+     * Return cached item content lambda or creates a new lambda and puts it in the cache.
+     */
+    fun getContent(index: Int, key: Any): @Composable () -> Unit {
+        val cachedContent = lambdasCache[key]
+        return if (cachedContent != null && cachedContent.index == index) {
+            cachedContent.content
+        } else {
+            val newContent = CachedItemContent(index, key)
+            lambdasCache[key] = newContent
+            newContent.content
+        }
+    }
+
+    private inner class CachedItemContent(
+        initialIndex: Int,
+        val key: Any
+    ) {
+        var index by mutableStateOf(initialIndex)
+
+        val content: @Composable () -> Unit = @Composable {
+            val itemsProvider = itemsProvider()
+            if (index < itemsProvider.itemsCount) {
+                val key = itemsProvider.getKey(index)
+                if (key == this.key) {
+                    val content = itemsProvider.getContent(index)
+                    saveableStateHolder.SaveableStateProvider(key, content)
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemInfo.kt
new file mode 100644
index 0000000..d1e049c
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemInfo.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+/**
+ * Contains useful information about an individual item in lazy layouts.
+ *
+ * @see LazyLayoutInfo
+ */
+internal interface LazyLayoutItemInfo {
+    /**
+     * The index of the item in the list.
+     */
+    val index: Int
+
+    /**
+     * The key of the item which was passed to the item() or items() function.
+     */
+    val key: Any
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
new file mode 100644
index 0000000..fe7a75e7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemsProvider.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+
+internal interface LazyLayoutItemsProvider {
+    /** Returns the content lambda for the given index and scope object */
+    fun getContent(index: Int): @Composable () -> Unit
+
+    /** The total number of items in the lazy layout (visible or not). */
+    val itemsCount: Int
+
+    /** Returns the key for the item on this index */
+    fun getKey(index: Int): Any
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureResult.kt
new file mode 100644
index 0000000..5391b0d
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureResult.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.ui.layout.MeasureResult
+
+internal interface LazyLayoutMeasureResult : MeasureResult, LazyLayoutInfo {
+    /**
+     * The list of [LazyLayoutItemInfo] representing all the currently visible items.
+     */
+    override val visibleItemsInfo: List<LazyLayoutItemInfo>
+
+    // TODO(popam): this should really be removed / derived implicitly from the placement block.
+    val composedButNotVisibleItemsIndices: List<Int>?
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchPolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchPolicy.kt
new file mode 100644
index 0000000..44dd216
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchPolicy.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.unit.Constraints
+
+/** Creates a [LazyLayoutPrefetchPolicy]. */
+@Composable
+internal fun rememberLazyLayoutPrefetchPolicy(): LazyLayoutPrefetchPolicy = remember {
+    LazyLayoutPrefetchPolicy()
+}
+
+/**
+ * Controller for lazy items prefetching, used by lazy layouts to instruct the prefetcher.
+ * TODO: This is currently supporting just one item, but it should rather be a queue.
+ */
+@Stable
+internal class LazyLayoutPrefetchPolicy {
+    internal var prefetcher: Subscriber? = null
+
+    /** Schedules a new item to prefetch, specified by [index]. */
+    fun scheduleForPrefetch(index: Int) = prefetcher?.scheduleForPrefetch(index)
+
+    /** Notifies the prefetcher that item with [index] is no longer likely to be needed. */
+    fun removeFromPrefetch(index: Int) = prefetcher?.removeFromPrefetch(index)
+
+    /** The constraints to be used for premeasuring the precomposed items. */
+    var constraints = Constraints()
+
+    interface Subscriber {
+        fun scheduleForPrefetch(index: Int)
+        fun removeFromPrefetch(index: Int)
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt
new file mode 100644
index 0000000..2ed35e0
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutState.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.layout.Remeasurement
+import androidx.compose.ui.layout.RemeasurementModifier
+import androidx.compose.ui.layout.SubcomposeMeasureScope
+
+/**
+ * Creates a [LazyLayoutState] that is remembered across recompositions.
+ */
+@Composable
+internal fun rememberLazyLayoutState(): LazyLayoutState {
+    return remember { LazyLayoutState() }
+}
+
+/**
+ * A state object that can be hoisted to interact and observe the state of the [LazyLayout].
+ *
+ * In most cases, this will be created via [rememberLazyLayoutState].
+*/
+@Stable
+internal class LazyLayoutState internal constructor() {
+    /**
+     * Information about the layout of the lazy layout, calculated during the latest layout pass.
+     */
+    val layoutInfo: LazyLayoutInfo get() = layoutInfoState.value
+
+    /** Backing state for [layoutInfo] */
+    internal val layoutInfoState = mutableStateOf<LazyLayoutInfo>(EmptyLazyLayoutInfo)
+
+    internal var layoutInfoNonObservable: LazyLayoutInfo = EmptyLazyLayoutInfo
+
+    /**
+     * Remeasures the lazy list now. This can be used, for example, in reaction to scrolling.
+     */
+    fun remeasure() = remeasurement?.forceRemeasure()
+
+    /**
+     * The [Remeasurement] object associated with our layout. It allows us to remeasure
+     * synchronously during scroll.
+     */
+    private var remeasurement: Remeasurement? = null
+
+    /**
+     * The modifier which provides [remeasurement].
+     */
+    internal val remeasurementModifier = object : RemeasurementModifier {
+        override fun onRemeasurementAvailable(remeasurement: Remeasurement) {
+            this@LazyLayoutState.remeasurement = remeasurement
+        }
+    }
+
+    /**
+     * The items provider of the lazy layout.
+     */
+    internal var itemsProvider: () -> LazyLayoutItemsProvider = { NoItemsProvider }
+
+    /**
+     * Listener to be notified after measurement - the prefetcher.
+     */
+    internal var onPostMeasureListener: LazyLayoutOnPostMeasureListener? = null
+}
+
+internal interface LazyLayoutInfo {
+    /** The items currently participating in the layout of the lazy layout. */
+    val visibleItemsInfo: List<LazyLayoutItemInfo>
+}
+
+private object EmptyLazyLayoutInfo : LazyLayoutInfo {
+    override val visibleItemsInfo = emptyList<LazyLayoutItemInfo>()
+}
+
+internal interface LazyLayoutOnPostMeasureListener {
+    fun SubcomposeMeasureScope.onPostMeasure(result: LazyLayoutMeasureResult)
+}
+
+private object NoItemsProvider : LazyLayoutItemsProvider {
+    override fun getContent(index: Int): () -> Unit = error("No items")
+
+    override val itemsCount = 0
+
+    override fun getKey(index: Int): Any = error("No items")
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt
new file mode 100644
index 0000000..115410b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyMeasurePolicy.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.SubcomposeMeasureScope
+import androidx.compose.ui.unit.Constraints
+
+/**
+ * Defines the measure and layout behaviour of a [LazyLayout].
+ */
+@Stable
+internal fun interface LazyMeasurePolicy {
+    fun MeasureScope.measure(
+        measurables: LazyMeasurablesProvider,
+        constraints: Constraints
+    ): LazyLayoutMeasureResult
+}
+
+/** A lazily evaluated "list" of [Measurable]s. */
+@Stable
+internal class LazyMeasurablesProvider internal constructor(
+    private val itemsProvider: LazyLayoutItemsProvider,
+    private val itemContentFactory: LazyLayoutItemContentFactory,
+    private val subcomposeMeasureScope: SubcomposeMeasureScope
+) {
+    operator fun get(index: Int): List<Measurable> {
+        val key = itemsProvider.getKey(index)
+        val itemContent = itemContentFactory.getContent(index, key)
+        return subcomposeMeasureScope.subcompose(key, itemContent)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
index 20e25b8..8f78d81 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
@@ -16,20 +16,6 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.ui.layout.SubcomposeLayoutState
-
 internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
 
 private data class DefaultLazyKey(private val index: Int)
-
-@Composable
-internal actual fun LazyListPrefetcher(
-    lazyListState: LazyListState,
-    stateOfItemsProvider: State<LazyListItemsProvider>,
-    itemContentFactory: LazyListItemContentFactory,
-    subcomposeLayoutState: SubcomposeLayoutState
-) {
-    // there is no prefetch implementation on desktop yet
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.desktop.kt
new file mode 100644
index 0000000..37ab057
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.desktop.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.layout.SubcomposeLayoutState
+
+@Composable
+internal actual fun LazyLayoutPrefetcher(
+    prefetchPolicy: LazyLayoutPrefetchPolicy,
+    state: LazyLayoutState,
+    itemContentFactory: LazyLayoutItemContentFactory,
+    subcomposeLayoutState: SubcomposeLayoutState
+) {
+    // there is no prefetch implementation on desktop yet
+}