Last active
February 23, 2023 22:58
-
-
Save ThePromoter/535620100cd322990a1e808b29c70c07 to your computer and use it in GitHub Desktop.
Prefetching lazy list items with coil
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface CoilPreloaderModelProvider<U> { | |
fun getPreloadItems(position: Int): List<U> | |
fun getPreloadRequest(item: U): ImageRequest? | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ItemLazyListPreloader( | |
private val items: List<Item> | |
) : CoilPreloader.PreloadModelProvider<String> { | |
override fun getPreloadItems(position: Int): List<String> { | |
if (position < 0 || position >= items.size) return emptyList() | |
return when (val item = items.getOrNull(position)) { | |
null -> emptyList() | |
else -> listOf(item.imageUrl) | |
} | |
} | |
override fun getPreloadRequest(itemUrl: String): ImageRequest = | |
ImageRequest.Builder(context).data(imageUrl) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable fun <T> LazyListPreloader( | |
listState: LazyListState, | |
preloadModelProvider: CoilPreloaderModelProvider<T>, | |
maxPreload: Int = 1 | |
) { | |
var lastEnd by remember { mutableStateOf(0) } | |
var lastStart by remember { mutableStateOf(0) } | |
var lastFirstVisible by remember { mutableStateOf(-1) } | |
var totalItemCount by remember { mutableStateOf(0) } | |
var wasIncreasing by remember { mutableStateOf(true) } | |
val preloadQueue = ArrayDeque<Disposable>(maxPreload + 1) | |
val context = LocalContext.current | |
val imageLoader = context.imageLoader | |
val scope = rememberCoroutineScope() | |
val cancelAll = { preloadQueue.forEach { it.dispose() } } | |
DisposableEffect(listState) { | |
scope.launch { | |
snapshotFlow { Triple(listState.firstVisibleItemIndex, listState.layoutInfo.visibleItemsInfo.size, listState.layoutInfo.totalItemsCount) } | |
.mapNotNull { (firstVisible, visibleCount, totalCount) -> | |
if (totalItemCount == 0 && totalCount == 0) return@mapNotNull null | |
totalItemCount = totalCount | |
val (start, isIncreasing) = when { | |
firstVisible > lastFirstVisible -> firstVisible + visibleCount to true | |
firstVisible < lastFirstVisible -> firstVisible to false | |
else -> return@mapNotNull null | |
} | |
lastFirstVisible = firstVisible | |
return@mapNotNull start to isIncreasing | |
} | |
.map { (start, isIncreasing) -> | |
if (wasIncreasing != isIncreasing) { | |
wasIncreasing = isIncreasing | |
cancelAll() | |
} | |
return@map start to start + (maxPreload.takeIf { isIncreasing } ?: -maxPreload) | |
} | |
.map { (from, to) -> | |
val isIncreasing = from < to | |
val (start, end) = when (isIncreasing) { | |
true -> from.coerceAtLeast(lastEnd).coerceIn(0, totalItemCount) to to.coerceAtMost(totalItemCount) | |
false -> to.coerceIn(0, totalItemCount) to from.coerceIn(lastStart, totalItemCount) | |
} | |
val itemsToLoad = when (isIncreasing) { | |
true -> (start until end).flatMap { preloadModelProvider.getPreloadItems(it) } | |
false -> (end - 1 downTo start).flatMap { preloadModelProvider.getPreloadItems(it) } | |
} | |
lastStart = start | |
lastEnd = end | |
return@map itemsToLoad to isIncreasing | |
} | |
.collectLatest { (items, isIncreasing) -> | |
for (item in items.takeIf { isIncreasing } ?: items.reversed()) { | |
val imageRequest = preloadModelProvider.getPreloadRequest(item) | |
if (imageRequest != null) preloadQueue.offer(imageLoader.enqueue(imageRequest)) | |
} | |
} | |
} | |
onDispose { cancelAll() } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable fun MyListComponent(items: List<Item>) { | |
val listState = rememberLazyListState() | |
LazyListPreloader( | |
listState = listState, | |
preloadModelProvider = ItemLazyListPreloader(items), | |
maxPreload = 5 | |
) | |
LazyColumn(state = listState) { | |
... | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment