

🌻 Landscapist is a highly optimized, pluggable Jetpack Compose and Kotlin Multiplatform image loading solution that fetches and displays network images, and compatibles with Glide, Coil, and Fresco. This library supports tracing image loading states, composing custom implementations, and some valuable animations, such as crossfades, blur transformation, and circular reveals. You can also configure and attach image-loading behaviors easily and fast with image plugins.
Who's using Landscapist?
👉 Check out who's using Landscapist.
Landscapist hits +1,100,000 downloads every month around the globe!

Why Landscapist?
Landscapist is built with a lot of consideration to improve the performance of image loadings in Jetpack Compose. Most composable functions of Landscapist are Restartable and Skippable, which indicates fairly improved recomposition performance according to the Compose compiler metrics. Also, the library performance was improved with and it supports many pluggable features, such as , , , , , , , , , , and .
See the Compose compiler metrics for Landscapist

Documentation
For comprehensive information about Landscapist, please refer to the official documentation.
Technical Content
For more details of the history, performance, customizability, and how to load network images and implement placeholders, animations, and transformations with Landscapist, check out Optimized Image Loading for Compose and Kotlin Multiplatform.
💝 Sponsors


Demo projects
You can see the use cases of this library in the repositories below:
- google/modernstorage: ModernStorage is a group of libraries that provide an abstraction layer over storage on Android to simplify its interactions.
Landscapist Core & Image
Landscapist now provides two foundational modules designed for Kotlin Multiplatform and Compose Multiplatform from the scratch, giving you full control over image loading across all platforms:
landscapist-core: A standalone, Kotlin Multiplatform image loading engine with built-in memory/disk caching, progressive loading, and network fetching via Ktor. Works on Android, iOS, Desktop, and Web without any UI dependencies.
landscapist-image: A Compose Multiplatform image component built on top of landscapist-core with full plugin support. Seamlessly works across all Compose Multiplatform targets.
These modules are perfect if you want a lightweight, customizable image loader without depending on Glide, Coil, or Fresco, with first-class support for all Kotlin Multiplatform and Compose Multiplatform targets.
Landscapist Core

The landscapist-core module is a complete, Kotlin Multiplatform image loading solution that works standalone without any UI dependencies. It provides:
- Network image loading via Ktor HTTP client.
Why Choose Landscapist Core?
Landscapist Core is exceptionally lightweight compared to other image loading libraries, making it the ideal choice for SDK and library developers who need to minimize their dependency footprint.
Core engine AAR size (release build), for the versions this repo depends on:
Reproduce with ./gradlew :landscapist-core:assembleRelease && ls -l landscapist-core/build/outputs/aar/landscapist-core-release.aar (320,771 bytes = 313 KiB). This is a single module's AAR, not the full transitive footprint.
Load-time and memory numbers are not published here because they depend on device, OS, and network. Run the included benchmarks (ImageLibraryBenchmark instrumentation test and the :benchmark-landscapist macrobenchmark) on your own hardware. See the performance comparison for methodology.
Setup
Add the dependency below to your module's build.gradle file:
dependencies {
implementation("com.github.skydoves:landscapist-core:$version")
}
For Kotlin Multiplatform, add to your module's build.gradle.kts:
sourceSets {
commonMain.dependencies {
implementation("com.github.skydoves:landscapist-core:$version")
}
}
All platform-specific Ktor engines are included automatically based on your target platforms.
Using Landscapist Core for Network Loading
You can use landscapist-core as a standalone image loader for fetching and caching network images without any UI dependencies. This is useful for pre-loading images, implementing custom image components, or using images in non-Compose contexts.
Android Example
This example demonstrates creating a Landscapist instance with custom cache sizes and loading an image from a URL. The result is delivered as a Flow, allowing you to handle loading, success, and failure states reactively.
Kotlin Multiplatform Example
For non-Android platforms (iOS, Desktop, Web), use the singleton instance which comes pre-configured with sensible defaults. This example shows a simple suspend function that loads an image and returns the ImageBitmap, suitable for use in shared Kotlin Multiplatform code.
import com.skydoves.landscapist.core.Landscapist
import com.skydoves.landscapist.core.ImageRequest
val landscapist = Landscapist.getInstance()
suspend fun loadImage(url: String): ImageBitmap? {
val request = ImageRequest.builder()
.model(url)
.build()
bitmap: ImageBitmap? =
landscapist.load(request).collect { result ->
(result ImageResult.Success) {
bitmap = result.
}
}
bitmap
}
Advanced Configuration
Customize the Landscapist instance with advanced options including network timeouts, memory optimizations, and event listeners. This example shows how to configure various aspects of the image loader to match your app's specific requirements.
Landscapist Image

The landscapist-image module provides a Compose Multiplatform UI component built on top of landscapist-core. It integrates seamlessly with the Landscapist plugin ecosystem and works across all Compose Multiplatform targets (Android, iOS, Desktop, Web).
Setup
Add the dependency below to your module's build.gradle file:
dependencies {
implementation("com.github.skydoves:landscapist-image:$version")
}
Note: This module depends on landscapist-core, which includes Ktor client automatically. No need to add Ktor dependencies separately.
For Kotlin Multiplatform:
sourceSets {
commonMain.dependencies {
implementation("com.github.skydoves:landscapist-image:$version")
}
}
LandscapistImage
Load images in Compose using LandscapistImage:
import com.skydoves.landscapist.image.LandscapistImage
import com.skydoves.landscapist.ImageOptions
LandscapistImage(
imageModel = { "https://example.com/image.jpg" },
modifier = Modifier.size(300.dp),
imageOptions = ImageOptions(
contentScale = ContentScale.Crop,
alignment = Alignment.Center
)
)
With Plugins
LandscapistImage supports all Landscapist plugins:
import com.skydoves.landscapist.components.rememberImageComponent
import com.skydoves.landscapist.placeholder.shimmer.ShimmerPlugin
import com.skydoves.landscapist.animation.crossfade.CrossfadePlugin
import com.skydoves.landscapist.transformation.blur.BlurTransformationPlugin
LandscapistImage(
imageModel = { imageUrl },
modifier = Modifier.fillMaxWidth(),
component = rememberImageComponent {
+ShimmerPlugin(
baseColor = Color.Gray,
highlightColor = Color.LightGray
)
+CrossfadePlugin(duration = 550)
+BlurTransformationPlugin(radius = 10)
}
)
Custom Landscapist Instance
Provide a custom Landscapist instance to your composition tree:
import com.skydoves.landscapist.core.Landscapist
import com.skydoves.landscapist.image.LocalLandscapist
import androidx.compose.runtime.CompositionLocalProvider
val customLandscapist = Landscapist.builder(context)
.config(
LandscapistConfig(
memoryCacheSize = 128 * 1024 * 1024L
)
)
.build()
CompositionLocalProvider(LocalLandscapist provides customLandscapist) {
LandscapistImage(
imageModel = { imageUrl },
)
}
Loading States
Handle loading, success, and failure states:
LandscapistImage(
imageModel = { imageUrl },
loading = {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
},
success = { state, painter ->
Image(
painter = painter,
contentDescription = "Loaded image"
)
},
failure = {
Text("Failed to load image")
}
)
Image State Changes
Monitor state changes with a callback:
var currentState by remember { mutableStateOf<LandscapistImageState>(LandscapistImageState.None) }
LandscapistImage(
imageModel = { imageUrl },
onImageStateChanged = { state ->
currentState = state
}
)
when (currentState) {
is LandscapistImageState.Loading -> { }
is LandscapistImageState.Success -> { }
is LandscapistImageState.Failure -> { }
else -> { }
}
Supported Image Sources
LandscapistImage supports various image sources including network URLs, local files, drawable resources, and more. See the Landscapist Image documentation for a complete list of supported image sources per platform.
Glide

Add the codes below to your root build.gradle file (not your module-level build.gradle file):
allprojects {
repositories {
mavenCentral()
}
}
Next, add the dependency below to your module's build.gradle file:
dependencies {
implementation("com.github.skydoves:landscapist-glide:2.11.0")
}
Note: Landscapist-Glide includes version 4.16.0 of Glide internally. So please make sure your project is using the same Glide version or exclude the Glide dependency to adapt yours. Also, please make sure the Jetpack Compose version on the release page.
GlideImage
You can load images simply by using GlideImage composable function as the following example below:
GlideImage(
imageModel = { imageUrl },
imageOptions = ImageOptions(
contentScale = ContentScale.Crop,
alignment = Alignment.Center
)
)
More Details for GlideImage
Coil

Add the dependency below to your module's build.gradle file:
dependencies {
implementation("com.github.skydoves:landscapist-coil:$version")
}
If you're targeting on Kotlin Multiplatform, add the dependency below to your module's build.gradle.kts file:
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.github.skydoves:landscapist-coil3:$version")
}
}
}
The coil3-landscapist package functions identically to the coil-landscapist package, with the key distinction being its focus on Kotlin Multiplatform. This enables the use of Coil3 across various platforms, including Android, iOS, and Desktop (JVM), facilitating a unified image loading experience across different environments.
Note: Please make sure your project uses the same Jetpack Compose version on the release page.
CoilImage
You can load images by using the CoilImage composable function as the following example below:
CoilImage(
imageModel = { imageUrl },
imageOptions = ImageOptions(
contentScale = ContentScale.Crop,
alignment = Alignment.Center
)
)
More Details for CoilImage
Fresco

Add the dependency below to your module's build.gradle file:
dependencies {
implementation("com.github.skydoves:landscapist-fresco:$version")
}
Note: Landscapist-Fresco includes version 3.1.0 of Fresco. So please make sure your project is using the same Fresco version or exclude the Fresco dependency to adapt yours. Also, please make sure the Jetpack Compose version on the release page.
Setup
To get started, you should set up Fresco with ImagePipelineConfig in your Application class. Generally, it's recommended initializing with OkHttpImagePipelineConfigFactory. Also, you can customize caching, networking, and thread pool strategies with your own ImagePipelineConfig. For more details, you can check out Using Other Network Layers.
class App : Application() {
override fun onCreate() {
super.onCreate()
val pipelineConfig =
OkHttpImagePipelineConfigFactory
.newBuilder(this, OkHttpClient.Builder().build())
.setDiskCacheEnabled(true)
.setDownsampleEnabled(true)
.setResizeAndRotateEnabledForNetwork(true)
.build()
Fresco.initialize(this, pipelineConfig)
}
}
FrescoImage
You can load images by using the FrescoImage composable function as the following example below:
FrescoImage(
imageUrl = stringImageUrl,
imageOptions = ImageOptions(
contentScale = ContentScale.Crop,
alignment = Alignment.Center
)
)
More Details for FrescoImage
ImageOptions
You can give image options to your image composable functions by passing ImageOptions instance like the below:
GlideImage(
..
imageOptions = ImageOptions(
contentScale = ContentScale.Crop,
alignment = Alignment.Center,
contentDescription = "main image",
colorFilter = null,
alpha = 1f
)
)
RequestSize
You can set the request size of your image by giving requestSize property as seen in the below:
GlideImage(
..
imageOptions = ImageOptions(requestSize = IntSize(800, 600)),
)
Listening image state changes
You can listen the image state changes by giving onImageStateChanged parameter to your image composable functions like the below:
GlideImage(
..
onImageStateChanged = {
when (it) {
GlideImageState.None -> ..
GlideImageState.Loading -> ..
is GlideImageState.Success -> ..
is GlideImageState.Failure -> ..
}
}
)
Note: You can use CoilImageState for CoilImage and FrescoImageState for FrescoImage.
DataSource
For the success state, you can trace the origin of the image with the DataSource parameter. DataSource represents the following source origins below:
- Memory: Represents an in-memory data source or cache (e.g. bitmap, ByteBuffer).
- Disk: Represents a disk-based data source (e.g. drawable resource, or File).
- Network: Represents a network-based data source.
- Unknown: Represents an unknown data source.
Custom Composables
You can execute your own composable functions depending on the three request states below:
- loading: Executed while loading an image.
- success: Executed if loading an image successfully.
- failure: Executed if fails to load an image (e.g. network error, wrong destination).
GlideImage(
imageModel = { imageUrl },
modifier = modifier,
loading = {
Box(modifier = Modifier.matchParentSize()) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
},
failure = {
Text(text = "image request failed.")
}
)
Also, you can customize the image content with our own composable function like the example below:
GlideImage(
imageModel = { imageUrl },
success = { state, painter ->
Image(
painter = painter,
modifier = Modifier.size(128.dp),
contentDescription = "Image"
)
},
..
)
Note: You can also use the custom Composables for CoilImage and FrescoImage.
Preview on Android Studio
Landscapist supports preview mode for each image library; Glide, Coil, and Fresco. You can show the preview image on your editor with a previewPlaceholder parameter as following:
GlideImage(
imageModel = { imageUrl },
modifier = Modifier.aspectRatio(0.8f),
previewPlaceholder = painterResource(id = R.drawable.poster)
)
Note: You can also use the the previewPlaceholder parameter for CoilImage and FrescoImage.
ImageComponent and ImagePlugin
You can compose supported image plugins by Landscapist or you can create your own image plugin that will be composed following the image loading state.
ImagePlugin is a pluggable compose interface that will be executed for loading images. ImagePlugin provides following types below:
- PainterPlugin: A pinter plugin interface to be composed with the given
Painter.
- LoadingStatePlugin: A pluggable state plugin that will be composed while the state is
ImageLoadState.Loading.
For example, you can implement your own LoadingStatePlugin that will be composed while loading an image like the below:
data class LoadingPlugin(val source: Any?) : ImagePlugin.LoadingStatePlugin {
@Composable
override fun compose(
modifier: Modifier,
imageOptions: ImageOptions?
): ImagePlugin = apply {
if (source != null && imageOptions != ) {
ImageBySource(
source = source,
modifier = modifier,
alignment = imageOptions.alignment,
contentDescription = imageOptions.contentDescription,
contentScale = imageOptions.contentScale,
colorFilter = imageOptions.colorFilter,
alpha = imageOptions.alpha
)
}
}
}
Next, you can compose plugins by adding them in the rememberImageComponent like the below:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
add(CircularRevealPlugin())
add(LoadingPlugin(source))
},
)
or you can just add plugins by using the + expression like the below:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+CircularRevealPlugin()
+LoadingPlugin(source)
},
)
LocalImageComponent
You can provide the same ImageComponent instance in the composable hierarchy by using imageComponent extension and LocalImageComponent like the below:
val component = imageComponent {
+CrossfadePlugin()
+PalettePlugin()
}
CompositionLocalProvider(LocalImageComponent provides component) {
..
}
Placeholder

The landscapist-placeholder package provides useful image plugins, such as loading & failure placeholder supports and shimmering animation.
To use placeholder supports, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-placeholder:$version")
}
ShimmerPlugin
You can implement a shimmering effect while loading an image by using the ShimmerPlugin as following the example below:
GlideImage(
imageModel = { imageUrl },
modifier = modifier,
component = rememberImageComponent {
+ShimmerPlugin(
Shimmer.Flash(
baseColor = Color.White,
highlightColor = Color.LightGray,
),
)
},
failure = {
Text(text = "image request failed.")
}
)
Note: You can also use the Shimmer effect for CoilImage and FrescoImage.
Shimmer sealed class provides following the three different types: Resonate, Fade, and Flash.
| Resonate | Fade | Flash |
|---|
 |  | |
PlaceholderPlugin
You can show your own placeholder while loading an image or when fails to load an image with PlaceholderPlugin.Loading and PlaceholderPlugin.Failure.
GlideImage(
..
component = rememberImageComponent {
+PlaceholderPlugin.Loading(painterResource(id = R.drawable.placeholder_loading))
+PlaceholderPlugin.Failure(painterResource(id = R.drawable.placeholder_failure))
},
)
Note: The source should be one of ImageBitmap, ImageVector, or Painter.
ThumbnailPlugin
Landscapist supports the thumbnail feature, which pre-loads and displays small sizes of images while loading the original image. So you can make users feel images loading faster and give images a nature loading effect while displaying an original image.
To show thumbnail, add the image plugin into your image component like the example below:
GlideImage(
..,
component = rememberImageComponent {
+ThumbnailPlugin()
},
)
You can also adjust the request sizes by giving the requestSize parameter:
component = rememberImageComponent {
+ThumbnailPlugin(IntSize(30 ,30))
},
Note: It's highly recommended to use a small size of the request size on the thumbnail plugin to load the pre-load images process faster.
Animation

The landscapist-animation package provides useful image plugins related to animations, such as crossfade and circular reveal animation.
To use animation supports, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-animation:$version")
}
Preview
| Circular Reveal | Crossfade |
|---|
 |  |
Crossfade Animation
You can implement the crossfade animation while drawing images with CrossfadePlugin as the following:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+CrossfadePlugin(
duration = 550
)
}
)
Note: You can also use the crossfade animation for CoilImage and FrescoImage.
Circular Reveal Animation
You can implement the circular reveal animation while drawing images with CircularRevealplugin as the following:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+CircularRevealPlugin(
duration = 350
)
}
)
Note: You can also use the Circular Reveal animation for CoilImage and FrescoImage.
Transformation

The landscapist-transformation package provides useful image transformation plugins, such as the blur effect.
To use transformation supports, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-transformation:$version")
}
BlurTransformationPlugin
You can implement the blur effect with BlurTransformationPlugin as the following:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+BlurTransformationPlugin(radius = 10)
}
)
Note: Landscapist's blur transformation falls back onto a CPU-based implementation to support older API levels. So you don't need to worry about API compatibilities and performance issues.
Palette

The landscapist-palette package provides useful image plugins related to palette, such as extracting primary color sets.
To use palette supports, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-palette:$version")
}
You can extract primary (theme) color profiles with PalettePlugin. You can check out Extract color profiles to see what kinds of colors can be extracted.
var palette by rememberPaletteState(null)
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+PalettePlugin { palette = it }
}
)
Crossfade(
targetState = palette,
modifier = Modifier
.padding(horizontal = 8.dp)
.size(45.dp)
) {
Box(
modifier = Modifier
.background(color = Color(it?.lightVibrantSwatch?.rgb ?: 0))
.fillMaxSize()
)
}
Also, you can customize attributes of PalettePlugin like the example below:
var palette by remember { mutableStateOf<Palette?>(null) }
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+PalettePlugin(
imageModel = poster.image,
useCache = true,
interceptor = {
it.addFilter { rgb, hsl ->
false
}
},
paletteLoadedListener = {
palette = it
}
)
}
)
Note: You can also use the Palette for CoilImage and FrescoImage.
Zoomable

The landscapist-zoomable package provides a ZoomablePlugin that enables zoom and pan gestures for images. This plugin supports both Android and Kotlin Multiplatform (iOS, Desktop).
To use zoomable supports, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-zoomable:$version")
}
ZoomablePlugin
You can implement zoom and pan gestures by adding ZoomablePlugin to your image component:
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+ZoomablePlugin()
}
)
ZoomableState
You can create and remember a ZoomableState with rememberZoomableState to customize the zoom behavior and access the current transformation state:
val zoomableState = rememberZoomableState(
config = ZoomableConfig(
minZoom = 30f,
maxZoom = 4f,
doubleTapZoom = 20f,
enableDoubleTapZoom = true,
)
)
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+ZoomablePlugin(state = zoomableState)
}
)
val currentScale = zoomableState.transformation.scale
val currentOffset = zoomableState.transformation.offset
Sub-Sampling
For very large images, Landscapist supports sub-sampling to efficiently display high-resolution images without running out of memory:
val zoomableState = rememberZoomableState(
config = ZoomableConfig(
enableSubSampling = true,
subSamplingConfig = SubSamplingConfig(
tileSize = 512.dp,
threshold = 2000.dp,
)
)
)
GlideImage(
imageModel = { poster.image },
component = rememberImageComponent {
+ZoomablePlugin(state = zoomableState)
}
)
Note: Sub-sampling is supported on Android (Glide, Coil3) and Kotlin Multiplatform (Coil3, targeting Android, iOS/macOS, and Destkop).
Image Gallery

The landscapist-image-gallery package provides high level, Compose Multiplatform UI components for building photo gallery experiences on top of Landscapist:
ImageGallery: a thumbnail grid built on LazyVerticalGrid with optional multi select, header and footer slots, and a custom content slot.
ImageViewer: a full screen pager with pinch to zoom, swipe to dismiss, and top bar, bottom bar, and page indicator overlays.
ImageSharedTransitionConfig: drop in shared element transition between gallery thumbnails and viewer pages.
By default, both components render their images using LandscapistImage, so they work on Android, iOS, Desktop, and Web without extra setup. You can still swap in Glide, Coil, Fresco, or any custom loader through the content slot.
To use the gallery components, add the dependency below:
dependencies {
implementation("com.github.skydoves:landscapist-image-gallery:$version")
}
For Kotlin Multiplatform:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.github.skydoves:landscapist-image-gallery:$version")
}
}
}
ImageGallery
ImageGallery displays a list of image models in a LazyVerticalGrid. The items can be anything accepted by Landscapist (URL strings, Uri, File, ByteArray, drawable resources, and so on).
import com.skydoves.landscapist.gallery.ImageGallery
ImageGallery(
images = imageUrls,
columns = GridCells.Fixed(3),
imageOptions = ImageOptions(contentScale = ContentScale.Crop),
component = rememberImageComponent {
+ShimmerPlugin(
shimmer = Shimmer.Resonate(
baseColor = Color.DarkGray,
highlightColor = Color.LightGray,
),
)
},
onImageClick = { index, imageModel ->
},
)
Because each tile is rendered by LandscapistImage under the hood, the full plugin ecosystem (Shimmer, Crossfade, Palette, and so on) is available through component. Use the content lambda to render tiles with another loader (Glide, Coil, Fresco).
Selection mode
Enable multi select by flipping selectable and tracking the selected indices yourself. Long pressing any tile enters selection mode, tapping tiles while in selection mode toggles them, and onImageClick is not fired while a selection is active.
var selectedIndices by remember { mutableStateOf(emptySet<Int>()) }
ImageGallery(
images = imageUrls,
selectable = true,
selectedIndices = selectedIndices,
onSelectionChanged = { selectedIndices = it },
selectionOverlay = { _, selected ->
if (selected) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f)),
contentAlignment = Alignment.TopEnd,
) {
Icon(
imageVector = Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.padding(6.dp).size(24.dp).clip(CircleShape),
)
}
}
},
header = { TopAppBar(title = { Text("Gallery") }) },
)
ImageViewer
ImageViewer is a full screen pager for viewing images one at a time. It includes horizontal paging, pinch and double tap zoom via ZoomablePlugin, swipe to dismiss, and slots for a top bar, bottom bar, and page indicator.
import com.skydoves.landscapist.gallery.ImageViewer
import com.skydoves.landscapist.gallery.rememberImageViewerState
val viewerState = rememberImageViewerState(
initialPage = startIndex,
pageCount = { imageUrls.size },
)
ImageViewer(
images = imageUrls,
state = viewerState,
zoomableConfig = ZoomableConfig(
minZoom = 1f,
maxZoom = 5f,
doubleTapZoom = 2f,
),
onDismiss = { navController.popBackStack() },
topBar = { current, total ->
TopAppBar(
backgroundColor = Color.Black.copy(alpha = 0.5f),
title = { Text("${current + 1} / $total", color = Color.White) },
)
},
)
Swipe to dismiss is automatically disabled while the current page is zoomed, so zoom and pan gestures are not interrupted. Use the content slot to render pages with another loader while keeping zoom, paging, and dismiss behavior intact.
Shared element transition
Pass the same ImageSharedTransitionConfig to both ImageGallery and ImageViewer to animate a tapped thumbnail into the full screen viewer and back on dismiss.
SharedTransitionLayout {
AnimatedContent(
targetState = showViewer,
transitionSpec = { fadeIn() togetherWith fadeOut() },
label = "gallery-viewer",
) { viewerVisible ->
val animatedContentScope = this
if (!viewerVisible) {
ImageGallery(
images = imageUrls,
onImageClick = { index, _ ->
selectedPage = index
showViewer = true
},
sharedTransition = ImageSharedTransitionConfig(
sharedTransitionScope = this,
animatedContentScope = animatedContentScope,
),
)
} {
ImageViewer(
images = imageUrls,
state = rememberImageViewerState(
initialPage = selectedPage,
pageCount = { imageUrls.size },
),
onDismiss = { showViewer = },
sharedTransition = ImageSharedTransitionConfig(
sharedTransitionScope = ,
animatedContentScope = animatedContentScope,
),
)
}
}
}
The same pattern works with NavHost. Use the AnimatedContentScope from each composable { ... } destination as the animatedContentScope. Override keyProvider when gallery indices don't match viewer pages, when the same model appears multiple times, or when you need to namespace keys across multiple galleries.
For the complete API reference (state holders, defaults, Kotlin Multiplatform targets, all overlay slots), see the Image Gallery documentation.
BOM

The landscapist Bill of Materials (BOM) lets you manage all of your landscapist library versions by specifying only the BOM’s version.
dependencies {
implementation("com.github.skydoves:landscapist-bom:$version")
implementation("com.github.skydoves:landscapist-glide")
implementation("com.github.skydoves:landscapist-placeholder")
implementation("com.github.skydoves:landscapist-palette")
implementation("com.github.skydoves:landscapist-transformation")
implementation("com.github.skydoves:landscapist-zoomable")
}
Taking Snapshot Images With Paparazzi
Paparazzi allows you to take snapshot images of your Composable functions without running them on physical devices. You can take proper snapshots images about your images with Paparazzi like the below:
paparazzi.snapshot {
CompositionLocalProvider(LocalInspectionMode provides true) {
GlideImage(
modifier = Modifier.fillMaxSize(),
imageModel = { ".." },
previewPlaceholder = painterResource(R.drawable.placeholder)
)
}
}
Who's using Landscapist?
If your project uses Landscapist, please let me know by creating a new issue! 🤗

Inspiration
This library was mostly inspired by Accompanist.
Accompanist is a group of libraries that contains some utilities which I've found myself copying around projects which use Jetpack Compose. Currently, it contains image loading and insets. You can get more variety and recent systems from the library maintained by Google.
Find this repository useful? :heart:
Support it by joining stargazers for this repository. :star:
Also follow me for my next creations! 🤩
License
Designed and developed by 2020 skydoves (Jaewoong Eum)
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.