I am developing an Android app using Jetpack Compose, where I need to display up to 9,000 images from the device storage in a LazyVerticalGrid. My current implementation works but struggles with performance and speed. Images load in batches, but the UI sometimes lags or images fail to load in time, especially with larger libraries.
Here's a simplified version of my implementation:
@Composable
fun MediaGridScreen(context: Context) {
val mediaList = remember { mutableStateListOf<Uri>() }
var lastLoadedIndex by remember { mutableStateOf(0) }
val batchSize = 100
// Load batches of media URIs
LaunchedEffect(lastLoadedIndex) {
val newMedia = loadMediaFromDevice(context.contentResolver, lastLoadedIndex, batchSize)
if (newMedia.isNotEmpty()) {
mediaList.addAll(newMedia)
lastLoadedIndex += newMedia.size
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize()
) {
items(mediaList.size) { index ->
MediaItem(mediaUri = mediaList[index])
}
}
}
@Composable
fun MediaItem(mediaUri: Uri) {
Image(
painter = rememberAsyncImagePainter(
model = mediaUri,
placeholder = painterResource(android.R.drawable.ic_menu_gallery)
),
contentDescription = null,
modifier = Modifier
.padding(4.dp)
.aspectRatio(1f)
.fillMaxWidth(),
contentScale = ContentScale.Crop
)
}
fun loadMediaFromDevice(contentResolver: ContentResolver, startIndex: Int, batchSize: Int): List<Uri> {
val mediaList = mutableListOf<Uri>()
val queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Images.Media._ID)
val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC LIMIT $batchSize OFFSET $startIndex"
val cursor = contentResolver.query(queryUri, projection, null, null, sortOrder)
cursor?.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val contentUri = Uri.withAppendedPath(queryUri, id.toString())
mediaList.add(contentUri)
}
}
return mediaList
}
What I've Tried:
Loading images in batches of 100 using MediaStore and updating the LazyVerticalGrid as new batches are loaded. Using rememberAsyncImagePainter from Coil to handle image loading. Displaying placeholders for images that are still loading. Problem:
Scrolling performance deteriorates with a large number of images. Some images take too long to load, making the UI feel unresponsive. The app sometimes struggles to handle 9,000+ images efficiently, even with batching. What I Need Help With:
How can I optimize the process of loading such a large number of images in Jetpack Compose? Are there better practices for managing large image grids to minimize UI lag? Should I consider prefetching, caching, or parallel loading to improve performance? Constraints:
Images must be displayed in a grid with dynamic scrolling. The solution should support a large number of images efficiently.
You can use Coil's memory caching to cache images in memory
val imageLoader = ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(0.25)
.build()
}
.build()
Also, by composing more images offscreen with LazyVerticalGrid
you can have more items in composition even if they are not visible yet and even though images are to be loaded first time.