Skip to content

Instantly share code, notes, and snippets.

@projectdelta6
Last active February 24, 2025 20:51
Show Gist options
  • Save projectdelta6/b768600a5087c8e1c7dc0c56c5d8147b to your computer and use it in GitHub Desktop.
Save projectdelta6/b768600a5087c8e1c7dc0c56c5d8147b to your computer and use it in GitHub Desktop.
Helper Extensions for using AndroidX Paging with Compose LazyList & LazyGrid
package //your.package
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
import androidx.compose.foundation.lazy.grid.LazyGridItemSpanScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
/**
* Adds a list of items from a [LazyPagingItems] object.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source
* @param key a factory of stable and unique keys representing the item. Using the same key
* for multiple items in the list is not allowed. Type of the key should be saveable
* via Bundle on Android. If null is passed the position in the list will represent the key.
* When you specify the key the scroll position will be maintained based on the key, which
* means if you add/remove items before the current visible item the item with the given key
* will be kept as the first visible one. This can be overridden by calling
* 'requestScrollToItem' on the 'LazyListState'.
* @param span a factory of the span for the item. The span of the item could be changed
* dynamically based on the item data. If null is passed the span will be 1.
* @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.
* @param itemContent the content displayed by a single item
* @param placeholderItemContent the content displayed by a single placeholder item
*
* @see LazyGridScope.items
*/
inline fun <T : Any> LazyGridScope.lazyPagingItems(
lazyPagingItems: LazyPagingItems<T>,
noinline key: ((item: T) -> Any)? = null,
noinline span: (LazyGridItemSpanScope.(item: T?) -> GridItemSpan)? = null,
noinline contentType: (item: T) -> Any? = { null },
crossinline placeholderItemContent: @Composable (LazyGridItemScope.() -> Unit) = {},
crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit
) = items(
count = lazyPagingItems.itemCount,
key = if (key != null) lazyPagingItems.itemKey { key(it) } else null,
span = if (span != null) {
{ span(lazyPagingItems.peek(it)) }
} else null,
contentType = lazyPagingItems.itemContentType { contentType(it) }
) {
val item = lazyPagingItems[it]
if (item != null) {
itemContent(item)
} else {
placeholderItemContent()
}
}
fun LazyGridScope.loadingStateItem(
key: Any,
span: (LazyGridItemSpanScope.() -> GridItemSpan)? = null
) {
item(
key = key,
contentType = key,
span = span
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.padding(vertical = 8.dp)
)
}
}
}
inline fun LazyGridScope.errorStateItem(
key: Any,
error: LoadState.Error,
crossinline errorText: @Composable (LoadState.Error) -> String,
crossinline retry: () -> Unit,
noinline span: (LazyGridItemSpanScope.() -> GridItemSpan)? = null
) {
item(
key = "${key}_Paging_error",
contentType = "${key}_Paging_error",
span = span
) {
Column(
modifier = modifier
.then(
Modifier
.padding(16.dp)
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = text
)
Button(
modifier = Modifier.padding(top = 16.dp),
darkText = true,
onClick = onRetry,
text = "Retry"
)
}
}
}
inline fun LazyGridScope.emptyStateItem(
key: Any,
crossinline emptyText: @Composable () -> String,
noinline span: (LazyGridItemSpanScope.() -> GridItemSpan)? = null
) {
item(
key = key,
contentType = key,
span = span
) {
Row(
modifier = modifier
.then(
Modifier
.padding(16.dp)
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = text
)
}
}
}
/**
* Item and loading state management for [LazyPagingItems] within a [LazyGridScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* This function uses [LazyGridScope.lazyPagingItems], passing the [itemKey], [itemContentType], [itemPlaceholderContent] and [itemContent] to it.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param errorText The text displayed when an error occurs.
* @param errorTextSpan The span for the error text. Defaults to [GridItemSpan] with [maxLineSpan][LazyGridItemSpanScope.maxLineSpan] to span all columns.
* @param retry The retry action for the error.
* @param emptyContent The content displayed when the list is empty and not loading.
* @param itemKey The key for the item, this should be unique for each item.
* @param itemSpan The span for the item.
* @param itemContentType The content type for the item, this should be unique for each item.
* @param itemPlaceholderContent The content displayed by a single placeholder item.
* @param itemContent The content displayed by a single item.
*
* @see LazyGridScope.lazyPagingItems
*/
inline fun <T : Any> LazyGridScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_prepend_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline appendLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_append_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline errorText: @Composable (LoadState.Error) -> String,
noinline errorTextSpan: (LazyGridItemSpanScope.() -> GridItemSpan)? = { GridItemSpan(maxLineSpan) },
crossinline retry: () -> Unit = { lazyPagingItems.retry() },
crossinline emptyContent: LazyGridScope.() -> Unit,
noinline itemKey: ((item: T) -> Any)? = null,
noinline itemSpan: (LazyGridItemSpanScope.(item: T?) -> GridItemSpan)? = null,
noinline itemContentType: (item: T) -> Any? = { null },
crossinline itemPlaceholderContent: @Composable (LazyGridItemScope.() -> Unit) = {},
crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
emptyContent = emptyContent,
errorContent = { key, error ->
errorStateItem(
key = key,
span = errorTextSpan,
error = error,
errorText = errorText,
retry = retry
)
},
itemKey = itemKey,
itemSpan = itemSpan,
itemContentType = itemContentType,
itemPlaceholderContent = itemPlaceholderContent,
itemContent = itemContent,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyGridScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* This function uses [LazyGridScope.lazyPagingItems], passing the [itemKey], [itemContentType], [itemPlaceholderContent] and [itemContent] to it.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param errorText The text displayed when an error occurs.
* @param errorTextSpan The span for the error text. Defaults to [GridItemSpan] with [maxLineSpan][LazyGridItemSpanScope.maxLineSpan] to span all columns.
* @param retry The retry action for the error.
* @param emptyText The text displayed when the list is empty and not loading.
* @param emptyTextSpan The span for the empty text. Defaults to [GridItemSpan] with [maxLineSpan][LazyGridItemSpanScope.maxLineSpan] to span all columns.
* @param itemKey The key for the item, this should be unique for each item.
* @param itemSpan The span for the item.
* @param itemContentType The content type for the item, this should be unique for each item.
* @param itemPlaceholderContent The content displayed by a single placeholder item.
* @param itemContent The content displayed by a single item.
*
* @see LazyGridScope.lazyPagingItems
*/
inline fun <T : Any> LazyGridScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_prepend_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline appendLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_append_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline errorText: @Composable (LoadState.Error) -> String,
noinline errorTextSpan: (LazyGridItemSpanScope.() -> GridItemSpan)? = { GridItemSpan(maxLineSpan) },
crossinline retry: () -> Unit = { lazyPagingItems.retry() },
crossinline emptyText: @Composable () -> String,
noinline emptyTextSpan: (LazyGridItemSpanScope.() -> GridItemSpan)? = { GridItemSpan(maxLineSpan) },
noinline itemKey: ((item: T) -> Any)? = null,
noinline itemSpan: (LazyGridItemSpanScope.(item: T?) -> GridItemSpan)? = null,
noinline itemContentType: (item: T) -> Any? = { null },
crossinline itemPlaceholderContent: @Composable (LazyGridItemScope.() -> Unit) = {},
crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
emptyContent = {
emptyStateItem(
key = "paging_empty",
span = emptyTextSpan,
emptyText = emptyText
)
},
errorContent = { key, error ->
errorStateItem(
key = key,
span = errorTextSpan,
error = error,
errorText = errorText,
retry = retry
)
},
itemKey = itemKey,
itemSpan = itemSpan,
itemContentType = itemContentType,
itemPlaceholderContent = itemPlaceholderContent,
itemContent = itemContent,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyGridScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* This function uses [LazyGridScope.lazyPagingItems], passing the [itemKey], [itemContentType], [itemPlaceholderContent] and [itemContent] to it.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param errorContent The content displayed when an error occurs, this provides the key and the error, the should be used for
* the [item][LazyGridScope.item] key as there could show multiple errors for the prepend, append and refresh states.
* @param emptyContent The content displayed when the list is empty and not loading.
* @param itemKey The key for the item, this should be unique for each item.
* @param itemSpan The span for the item.
* @param itemContentType The content type for the item, this should be unique for each item.
* @param itemPlaceholderContent The content displayed by a single placeholder item.
* @param itemContent The content displayed by a single item.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
*
* @see LazyGridScope.lazyPagingItems
*/
inline fun <T : Any> LazyGridScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_prepend_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline appendLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_append_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline errorContent: LazyGridScope.(key: PagingErrorType, error: LoadState.Error) -> Unit,
crossinline emptyContent: LazyGridScope.() -> Unit,
noinline itemKey: ((item: T) -> Any)? = null,
noinline itemSpan: (LazyGridItemSpanScope.(item: T?) -> GridItemSpan)? = null,
noinline itemContentType: (item: T) -> Any? = { null },
crossinline itemPlaceholderContent: @Composable (LazyGridItemScope.() -> Unit) = {},
crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
errorContent = errorContent,
emptyContent = emptyContent,
itemsContent = {
lazyPagingItems(
lazyPagingItems = lazyPagingItems,
key = itemKey,
span = itemSpan,
contentType = itemContentType,
placeholderItemContent = itemPlaceholderContent,
itemContent = itemContent
)
},
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyGridScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param errorText The text displayed when an error occurs.
* @param errorTextSpan The span for the error text. Defaults to [GridItemSpan] with [maxLineSpan][LazyGridItemSpanScope.maxLineSpan] to span all columns.
* @param emptyText The text displayed when the list is empty and not loading.
* @param emptyTextSpan The span for the empty text. Defaults to [GridItemSpan] with [maxLineSpan][LazyGridItemSpanScope.maxLineSpan] to span all columns.
* @param itemsContent The content displayed for the items.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
*/
inline fun <T : Any> LazyGridScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_prepend_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline appendLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_append_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline errorText: @Composable (LoadState.Error) -> String,
noinline errorTextSpan: (LazyGridItemSpanScope.() -> GridItemSpan)? = { GridItemSpan(maxLineSpan) },
crossinline retry: () -> Unit = { lazyPagingItems.retry() },
crossinline emptyText: @Composable () -> String,
noinline emptyTextSpan: (LazyGridItemSpanScope.() -> GridItemSpan)? = { GridItemSpan(maxLineSpan) },
crossinline itemsContent: LazyGridScope.(lazyPagingItems: LazyPagingItems<T>) -> Unit
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
emptyContent = {
emptyStateItem(
key = "paging_empty",
span = emptyTextSpan,
emptyText = emptyText
)
},
errorContent = { key, error ->
errorStateItem(
key = key,
span = errorTextSpan,
error = error,
errorText = errorText,
retry = retry
)
},
itemsContent = itemsContent,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyGridScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param errorContent The content displayed when an error occurs, this provides the key and the error, the should be used for
* the [item][LazyGridScope.item] key as there could show multiple errors for the prepend, append and refresh states.
* @param emptyContent The content displayed when the list is empty and not loading.
* @param itemsContent The content displayed for the items.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
*/
inline fun <T : Any> LazyGridScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_prepend_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline appendLoadingContent: LazyGridScope.() -> Unit = {
loadingStateItem(key = "paging_append_loading", span = { GridItemSpan(maxLineSpan) })
},
crossinline errorContent: LazyGridScope.(key: PagingErrorType, error: LoadState.Error) -> Unit,
crossinline emptyContent: LazyGridScope.() -> Unit,
crossinline itemsContent: LazyGridScope.(lazyPagingItems: LazyPagingItems<T>) -> Unit
) {
val prependState = lazyPagingItems.loadState.prepend
val appendState = lazyPagingItems.loadState.append
val refreshState = lazyPagingItems.loadState.refresh
val loading = (listOf(prependState, appendState, refreshState).firstOrNull { it.isLoading() } as LoadState.Loading?) != null
if (!loading && refreshState.isError()) {
errorContent(PagingErrorType.REFRESH, refreshState)
} else {
if (!usingPlaceholders && prependState.isLoading()) {
prependLoadingContent()
} else if (prependState.isError()) {
errorContent(PagingErrorType.PREPEND, prependState)
}
if (lazyPagingItems.itemCount == 0 && !loading) {
emptyContent()
} else {
itemsContent(lazyPagingItems)
}
if (!usingPlaceholders && appendState.isLoading()) {
appendLoadingContent()
} else if (appendState.isError()) {
errorContent(PagingErrorType.APPEND, appendState)
}
}
}
package //your.package
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
/**
* Adds a list of items from a [LazyPagingItems] object.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source
* @param key a factory of stable and unique keys representing the item. Using the same key
* for multiple items in the list is not allowed. Type of the key should be saveable
* via Bundle on Android. If null is passed the position in the list will represent the key.
* When you specify the key the scroll position will be maintained based on the key, which
* means if you add/remove items before the current visible item the item with the given key
* will be kept as the first visible one. This can be overridden by calling
* 'requestScrollToItem' on the 'LazyListState'.
* @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.
* @param itemContent the content displayed by a single item
* @param placeholderItemContent the content displayed by a single placeholder item
*
* @see LazyListScope.items
*/
inline fun <T : Any> LazyListScope.lazyPagingItems(
lazyPagingItems: LazyPagingItems<T>,
noinline key: ((item: T) -> Any)? = null,
noinline contentType: (item: T) -> Any? = { null },
crossinline placeholderItemContent: @Composable (LazyItemScope.() -> Unit) = {},
crossinline itemContent: @Composable (LazyItemScope.(item: T) -> Unit)
) = items(
count = lazyPagingItems.itemCount,
key = if (key != null) lazyPagingItems.itemKey { key(it) } else null,
contentType = lazyPagingItems.itemContentType { contentType(it) }
) {
val item = lazyPagingItems[it]
if (item != null) {
itemContent(item)
} else {
placeholderItemContent()
}
}
@OptIn(ExperimentalContracts::class)
fun LoadState.isLoading(): Boolean {
contract { returns(true) implies (this@isLoading is LoadState.Loading) }
return this is LoadState.Loading
}
@OptIn(ExperimentalContracts::class)
fun LoadState.isError(): Boolean {
contract { returns(true) implies (this@isError is LoadState.Error) }
return this is LoadState.Error
}
fun LazyListScope.loadingStateItem(
key: Any
) {
item(
key = key,
contentType = key
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.padding(vertical = 8.dp)
)
}
}
}
inline fun LazyListScope.errorStateItem(
key: Any,
error: LoadState.Error,
crossinline errorText: @Composable (LoadState.Error) -> String,
crossinline retry: () -> Unit
) {
item(
key = "${key}_Paging_error",
contentType = "${key}_Paging_error"
) {
Column(
modifier = modifier
.then(
Modifier
.padding(16.dp)
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = text
)
Button(
modifier = Modifier.padding(top = 16.dp),
darkText = true,
onClick = onRetry,
text = "Retry"
)
}
}
}
inline fun LazyListScope.emptyStateItem(
key: Any,
crossinline emptyText: @Composable () -> String
) {
item(
key = key,
contentType = key
) {
Row(
modifier = modifier
.then(
Modifier
.padding(16.dp)
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = text
)
}
}
}
/**
* Item and loading state management for [LazyPagingItems] within a [LazyListScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* This function uses [LazyListScope.lazyPagingItems], passing the [itemKey], [itemContentType], [itemPlaceholderContent] and [itemContent] to it.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param refreshLoadingContent The content displayed when the refresh is loading, this defaults to null.
* @param errorText The text displayed when an error occurs.
* @param emptyText The text displayed when the list is empty and not loading.
* @param retry The retry action to perform when an error occurs.
* @param itemKey The key for the item, this should be unique for each item.
* @param itemContentType The content type for the item, this should be unique for each item.
* @param itemPlaceholderContent The content displayed by a single placeholder item.
* @param itemContent The content displayed by a single item.
*
* @see LazyListScope.lazyPagingItems
*/
inline fun <T : Any> LazyListScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_prepend_loading")
},
crossinline appendLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_append_loading")
},
noinline refreshLoadingContent: (LazyListScope.() -> Unit)? = null,
crossinline emptyText: @Composable () -> String,
crossinline errorText: @Composable (LoadState.Error) -> String,
crossinline retry: () -> Unit = { lazyPagingItems.retry() },
noinline itemKey: ((item: T) -> Any)? = null,
noinline itemContentType: (item: T) -> Any? = { null },
crossinline itemPlaceholderContent: @Composable (LazyItemScope.() -> Unit) = {},
crossinline itemContent: @Composable (LazyItemScope.(item: T) -> Unit)
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent,
refreshLoadingContent = refreshLoadingContent,
errorContent = { key, error ->
errorStateItem("${key}_Paging_error", error, errorText, retry)
},
emptyContent = {
emptyStateItem("paging_empty", emptyText)
},
itemKey = itemKey,
itemContentType = itemContentType,
itemPlaceholderContent = itemPlaceholderContent,
itemContent = itemContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyListScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param refreshLoadingContent The content displayed when the refresh is loading, this defaults to null.
* @param errorText The text displayed when an error occurs.
* @param emptyText The text displayed when the list is empty and not loading.
* @param retry The retry action to perform when an error occurs.
* @param itemsContent The content displayed for the items.
*/
inline fun <T : Any> LazyListScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_prepend_loading")
},
crossinline appendLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_append_loading")
},
noinline refreshLoadingContent: (LazyListScope.() -> Unit)? = null,
crossinline emptyText: @Composable () -> String,
crossinline errorText: @Composable (LoadState.Error) -> String,
crossinline retry: () -> Unit = { lazyPagingItems.retry() },
crossinline itemsContent: LazyListScope.(lazyPagingItems: LazyPagingItems<T>) -> Unit
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent,
refreshLoadingContent = refreshLoadingContent,
errorContent = { key, error ->
errorStateItem("${key}_Paging_error", error, errorText, retry)
},
emptyContent = {
emptyStateItem("paging_empty", emptyText)
},
itemsContent = itemsContent
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyListScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* This function uses [LazyListScope.lazyPagingItems], passing the [itemKey], [itemContentType], [itemPlaceholderContent] and [itemContent] to it.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param refreshLoadingContent The content displayed when the refresh is loading, this defaults to null.
* @param errorContent The content displayed when an error occurs, this provides the key and the error, the should be used for
* the [item][LazyListScope.item] key as there could show multiple errors for the prepend, append and refresh states.
* @param emptyContent The content displayed when the list is empty and not loading.
* @param itemKey The key for the item, this should be unique for each item.
* @param itemContentType The content type for the item, this should be unique for each item.
* @param itemPlaceholderContent The content displayed by a single placeholder item.
* @param itemContent The content displayed by a single item.
*
* @see LazyListScope.lazyPagingItems
*/
inline fun <T : Any> LazyListScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_prepend_loading")
},
crossinline appendLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_append_loading")
},
noinline refreshLoadingContent: (LazyListScope.() -> Unit)? = null,
crossinline errorContent: LazyListScope.(key: PagingErrorType, error: LoadState.Error) -> Unit,
crossinline emptyContent: LazyListScope.() -> Unit,
noinline itemKey: ((item: T) -> Any)? = null,
noinline itemContentType: (item: T) -> Any? = { null },
crossinline itemPlaceholderContent: @Composable (LazyItemScope.() -> Unit) = {},
crossinline itemContent: @Composable (LazyItemScope.(item: T) -> Unit)
) = lazyPagingItemsWithStates(
lazyPagingItems = lazyPagingItems,
usingPlaceholders = usingPlaceholders,
prependLoadingContent = prependLoadingContent,
appendLoadingContent = appendLoadingContent,
refreshLoadingContent = refreshLoadingContent,
errorContent = errorContent,
emptyContent = emptyContent,
itemsContent = {
lazyPagingItems(
lazyPagingItems = lazyPagingItems,
key = itemKey,
contentType = itemContentType,
placeholderItemContent = itemPlaceholderContent,
itemContent = itemContent
)
}
)
/**
* Item and loading state management for [LazyPagingItems] within a [LazyListScope].
*
* This function will automatically handle the loading, error, empty states and the items.
*
* @param lazyPagingItems The [LazyPagingItems] object to use as the data source.
* @param usingPlaceholders If using Placeholders, then the [prependLoadingContent] and [appendLoadingContent] will not be displayed.
* @param prependLoadingContent The content displayed when the prepend is loading, this defaults to a [LoadingState].
* @param appendLoadingContent The content displayed when the append or refresh is loading, this defaults to a [LoadingState].
* @param refreshLoadingContent The content displayed when the refresh is loading, this defaults to null.
* @param errorContent The content displayed when an error occurs, this provides the key and the error, the should be used for
* the [item][LazyListScope.item] key as there could show multiple errors for the prepend, append and refresh states.
* @param emptyContent The content displayed when the list is empty and not loading.
* @param itemsContent The content displayed for the items.
*/
inline fun <T : Any> LazyListScope.lazyPagingItemsWithStates(
lazyPagingItems: LazyPagingItems<T>,
usingPlaceholders: Boolean = false,
crossinline prependLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_prepend_loading")
},
crossinline appendLoadingContent: LazyListScope.() -> Unit = {
loadingStateItem("paging_append_loading")
},
noinline refreshLoadingContent: (LazyListScope.() -> Unit)? = null,
crossinline errorContent: LazyListScope.(key: PagingErrorType, error: LoadState.Error) -> Unit,
crossinline emptyContent: LazyListScope.() -> Unit,
crossinline itemsContent: LazyListScope.(lazyPagingItems: LazyPagingItems<T>) -> Unit
) {
val prependState = lazyPagingItems.loadState.prepend
val appendState = lazyPagingItems.loadState.append
val refreshState = lazyPagingItems.loadState.refresh
val loading = (listOf(
prependState,
appendState,
refreshState
).firstOrNull { it.isLoading() } as LoadState.Loading?) != null
if (!loading && refreshState.isError()) {
errorContent(PagingErrorType.REFRESH, refreshState)
} else {
if (!usingPlaceholders &&prependState.isLoading()) {
prependLoadingContent()
} else if (prependState.isError()) {
errorContent(PagingErrorType.PREPEND, prependState)
}
if (refreshLoadingContent != null && refreshState.isLoading()) {
refreshLoadingContent()
}
if (lazyPagingItems.itemCount == 0 && !loading) {
emptyContent()
} else {
itemsContent(lazyPagingItems)
}
if (!usingPlaceholders &&appendState.isLoading()) {
appendLoadingContent()
} else if (appendState.isError()) {
errorContent(PagingErrorType.APPEND, appendState)
}
}
}
package //your.package
enum class PagingErrorType {
PREPEND,
APPEND,
REFRESH
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment