Implement LazyPagingItem itemContenType as helper function
Added itemContentType API which returns a lambda that can be plugged into `contentType` params for Lazy scopes.
If no contentType is provided, defaults to null contentType.
Test: Compose demos
Test: ./gradlew paging:paging-compose:cC
Bug: 259385813
Relnote: "LazyPagingItems has a new itemContentType API which returns a contentType implementation that can be plugged into the `contentType` params of Lazy scopes. If no contentType is provided, defaults to `null` contentType for all paging items."
Change-Id: Ib04f099111605af368c5b025d84a085ab4251fb6
diff --git a/paging/paging-compose/api/current.txt b/paging/paging-compose/api/current.txt
index 3ae7d53..da8a532 100644
--- a/paging/paging-compose/api/current.txt
+++ b/paging/paging-compose/api/current.txt
@@ -2,6 +2,7 @@
package androidx.paging.compose {
public final class LazyFoundationExtensionsKt {
+ method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemContentType(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? contentType);
method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemKey(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? key);
}
diff --git a/paging/paging-compose/api/public_plus_experimental_current.txt b/paging/paging-compose/api/public_plus_experimental_current.txt
index 3ae7d53..da8a532 100644
--- a/paging/paging-compose/api/public_plus_experimental_current.txt
+++ b/paging/paging-compose/api/public_plus_experimental_current.txt
@@ -2,6 +2,7 @@
package androidx.paging.compose {
public final class LazyFoundationExtensionsKt {
+ method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemContentType(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? contentType);
method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemKey(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? key);
}
diff --git a/paging/paging-compose/api/restricted_current.txt b/paging/paging-compose/api/restricted_current.txt
index 3ae7d53..da8a532 100644
--- a/paging/paging-compose/api/restricted_current.txt
+++ b/paging/paging-compose/api/restricted_current.txt
@@ -2,6 +2,7 @@
package androidx.paging.compose {
public final class LazyFoundationExtensionsKt {
+ method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemContentType(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? contentType);
method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemKey(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? key);
}
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
index a5d27d0..c63c4b7 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/PagingFoundationDemos.kt
@@ -20,6 +20,7 @@
import androidx.compose.integration.demos.common.DemoCategory
import androidx.paging.compose.samples.PagingWithHorizontalPager
import androidx.paging.compose.samples.PagingWithLazyGrid
+import androidx.paging.compose.samples.PagingWithLazyList
import androidx.paging.compose.samples.PagingWithVerticalPager
val PagingFoundationDemos = DemoCategory(
@@ -27,6 +28,7 @@
listOf(
ComposableDemo("Paging with HorizontalPager") { PagingWithHorizontalPager() },
ComposableDemo("Paging with VerticalPager") { PagingWithVerticalPager() },
- ComposableDemo("Paging with LazyGrid") { PagingWithLazyGrid() }
+ ComposableDemo("Paging with LazyGrid") { PagingWithLazyGrid() },
+ ComposableDemo("Paging with LazyColumn") { PagingWithLazyList() },
)
)
\ No newline at end of file
diff --git a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
index 5867a4c..8b55ca8 100644
--- a/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
+++ b/paging/paging-compose/samples/src/main/java/androidx/paging/compose/samples/PagingFoundationSample.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.pager.HorizontalPager
@@ -41,6 +42,7 @@
import androidx.paging.PagingConfig
import androidx.paging.TestPagingSource
import androidx.paging.compose.collectAsLazyPagingItems
+import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
val pager = Pager(
@@ -94,6 +96,39 @@
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it },
+ contentType = lazyPagingItems.itemContentType { "MyPagingItems" }
+ ) { index ->
+ val item = lazyPagingItems[index]
+ PagingItem(item = item)
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Sampled
+@Composable
+public fun PagingWithLazyList() {
+ val lazyPagingItems = pager.collectAsLazyPagingItems()
+
+ LazyColumn {
+ stickyHeader(
+ key = "Header",
+ contentType = "My Header",
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(bottom = 10.dp)
+ .background(Color.Red)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(text = "Header", fontSize = 32.sp)
+ }
+ }
+ items(
+ count = lazyPagingItems.itemCount,
+ key = lazyPagingItems.itemKey { it },
+ contentType = lazyPagingItems.itemContentType { "MyPagingItems" }
) { index ->
val item = lazyPagingItems[index]
PagingItem(item = item)
diff --git a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index c0a818d..1e59f5a 100644
--- a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -61,6 +61,8 @@
val rule = createComposeRule()
val items = (1..10).toList().map { it }
+ private val itemsSizePx = 30f
+ private val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
private fun createPager(
config: PagingConfig = PagingConfig(
@@ -77,6 +79,19 @@
return Pager(config = config, pagingSourceFactory = pagingSourceFactory)
}
+ private fun createPagerWithPlaceholders(
+ config: PagingConfig = PagingConfig(
+ pageSize = 1,
+ enablePlaceholders = true,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 0,
+ )
+ ) = Pager(
+ config = config,
+ pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) }
+ )
+
@Test
fun lazyPagingInitialLoadState() {
val pager = createPager()
@@ -251,11 +266,8 @@
@Test
fun differentContentTypes() {
- val pager = createPager()
-
+ val pager = createPagerWithPlaceholders()
lateinit var state: LazyListState
- val itemsSizePx = 30f
- val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
rule.setContent {
state = rememberLazyListState()
@@ -267,20 +279,17 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
- val content = @Composable { tag: String ->
- Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
- }
item(contentType = "not-to-reuse--1") {
- content("-1")
+ Content("-1")
}
item(contentType = "reuse") {
- content("0")
+ Content("0")
}
items(
items = lazyPagingItems,
contentType = { if (it == 8) "reuse" else "not-to-reuse-$it" }
) {
- content("$it")
+ Content("$it")
}
}
}
@@ -302,7 +311,7 @@
rule.runOnIdle {
runBlocking {
state.scrollToItem(8)
- // item 8 should reuse slot 1
+ // item 8 should reuse slot 0
}
}
@@ -321,22 +330,9 @@
}
@Test
- fun nullContentTypeWithPlaceholders() {
- val config = PagingConfig(
- pageSize = 1,
- enablePlaceholders = true,
- maxSize = 200,
- initialLoadSize = 3,
- prefetchDistance = 0,
- )
- val pager = Pager(
- config = config,
- pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) }
- )
-
+ fun nullItemContentType() {
+ val pager = createPagerWithPlaceholders()
lateinit var state: LazyListState
- val itemsSizePx = 30f
- val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
var loadedItem6 = false
@@ -351,22 +347,19 @@
}
LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
- val content = @Composable { tag: String ->
- Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
- }
item(contentType = "not-to-reuse--1") {
- content("-1")
+ Content("-1")
}
- item(contentType = null) {
- content("0")
+ // to be reused later by placeholder item
+ item(contentType = PagingPlaceholderContentType) {
+ Content("0")
}
items(
items = lazyPagingItems,
- key = { it },
- // item 7 would be null, which should default to contentType null
+ // item 7 would be null, which should default to PagingPlaceholderContentType
contentType = { "not-to-reuse-$it" }
) {
- content("$it")
+ Content("$it")
}
}
}
@@ -392,7 +385,7 @@
rule.runOnIdle {
runBlocking {
state.scrollToItem(6)
- // item 7 which is null should reuse slot 1
+ // item 7 which is null should reuse slot 0
}
}
@@ -402,9 +395,69 @@
// node reused
rule.onNodeWithTag("0")
.assertDoesNotExist()
- rule.onNodeWithTag("null")
+ }
+
+ @Test
+ fun nullContentType() {
+ val pager = createPagerWithPlaceholders()
+ lateinit var state: LazyListState
+
+ rule.setContent {
+ state = rememberLazyListState()
+
+ val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ for (i in 0 until lazyPagingItems.itemCount) {
+ lazyPagingItems[i]
+ }
+
+ LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
+ item(contentType = "not-to-reuse--1") {
+ Content("-1")
+ }
+ // to be reused later by real items
+ item(contentType = null) {
+ Content("0")
+ }
+ items(
+ items = lazyPagingItems,
+ // should default to null
+ contentType = null
+ ) {
+ Content("$it")
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ runBlocking {
+ state.scrollToItem(2)
+ // now items -1 and 0 are put into reusables
+ }
+ }
+
+ rule.onNodeWithTag("-1")
.assertExists()
.assertIsNotDisplayed()
+ rule.onNodeWithTag("0")
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ rule.runOnIdle {
+ runBlocking {
+ // item 4
+ state.scrollToItem(3)
+ }
+ }
+
+ rule.onNodeWithTag("-1")
+ .assertExists()
+ .assertIsNotDisplayed()
+ // node reused
+ rule.onNodeWithTag("0")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("4")
+ .assertExists()
+ .assertIsDisplayed()
}
@Test
@@ -848,4 +901,9 @@
items
)
}
+
+ @Composable
+ private fun Content(tag: String) {
+ Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
+ }
}
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt
index 1f00063..e4c2610 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyFoundationExtensions.kt
@@ -50,3 +50,33 @@
}
}
}
+
+/**
+ * Returns a factory for the content type of the item.
+ *
+ * ContentTypes are generated with the contentType lambda that is passed in. If null is passed in,
+ * contentType of all items will default to `null`.
+ * If [PagingConfig.enablePlaceholders] is true, LazyPagingItems may return null items. Null
+ * items will automatically default to placeholder contentType.
+ *
+ * This factory can be applied to Lazy foundations such as [LazyGridScope.items] or Pagers.
+ * Examples:
+ * @sample androidx.paging.compose.samples.PagingWithLazyGrid
+ * @sample androidx.paging.compose.samples.PagingWithLazyList
+ *
+ * @param [contentType] a factory of the content types for the item. The item compositions of
+ * the same type could be reused more efficiently. Note that null is a valid type and items of
+ * such type will be considered compatible.
+ */
+public fun <T : Any> LazyPagingItems<T>.itemContentType(
+ contentType: ((item: @JvmSuppressWildcards T) -> Any?)? = null
+): (index: Int) -> Any? {
+ return { index ->
+ if (contentType == null) {
+ null
+ } else {
+ val item = peek(index)
+ if (item == null) PagingPlaceholderContentType else contentType(item)
+ }
+ }
+}
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index fa2cbda..2614263 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -308,12 +308,7 @@
items(
count = items.itemCount,
key = items.itemKey(key),
- contentType = { index ->
- if (contentType == null) null else {
- val item = items.peek(index)
- if (item == null) null else contentType(item)
- }
- }
+ contentType = items.itemContentType(contentType)
) { index ->
itemContent(items[index])
}
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholderKey.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholders.kt
similarity index 96%
rename from paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholderKey.kt
rename to paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholders.kt
index ac98161..417f2e3 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholderKey.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/PagingPlaceholders.kt
@@ -41,4 +41,6 @@
override fun newArray(size: Int) = arrayOfNulls<PagingPlaceholderKey?>(size)
}
}
-}
\ No newline at end of file
+}
+
+internal object PagingPlaceholderContentType