ImagePicker is a Jetpack Compose library for displaying and selecting media from the device gallery.
ImagePicker uses a declarative DSL structure to define screens within a navigation graph, similar to NavHost and composable in Jetpack Navigation
- π§© DSL-based Navigation Graph: Declare screens inside
ImagePickerNavHostlikeNavHost - π¦ Fully customizable UI for album selector, preview bar, image cells and preview screen
- ποΈ Multi-selection with drag gesture support
- π’ Visual selection order (e.g., 1st, 2nd...)
- π· Camera integration with optional auto-select after capture
- πΌοΈ Full Preview screen for selected images
- π Pagination for smooth loading of large image sets (via Paging 3)
- ποΈ Album-based grouping with dynamic filtering
Step 1. Add it in your root build.gradle at the end of repositories:
dependencyResolutionManagement {
...
repositories {
maven { url 'https://jitpack.io' }
}
}Step 2. Add the dependency
dependencies {
implementation 'com.github.minsuk-jang:ImagePicker:1.0.15'
}Make sure to include the following in your AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA" />Declare your image picker UI using ImagePickerNavHost, just like NavHost in Jetpack Navigation.
Each slot (albumTopBar, previewTopBar, cellContent) and PreviewScreen gives access to its own custom scope to help you build highly flexible UIs.
ImagePickerNavHost(state = state) {
ImagePickerScreen(
albumTopBar = { ... },
previewTopBar = { ... },
cellContent = { ... }
)
PreviewScreen {
// Full-screen preview UI
}
}π· Example Output:
Each slot in ImagePickerScreen or PreviewScreen is powered by a custom scope. These scopes provide the necessary state and event handlers you need to build fully customized UIs.
Below is a breakdown of each slot, its associated scope, and what you can do inside it.
Use this slot to show album-related UI such as a dropdown or album selector. The ImagePickerAlbumScope gives you access to:
| Property / Function | Description |
|---|---|
albums: List<Album> |
List of all albums on the device |
selectedAlbum: Album? |
Currently selected album |
onClick(album: Album) |
Select the given album |
Example usage:
albumTopBar = {
var expanded by remember { mutableStateOf(false) }
Box {
Text(
text = selectedAlbum?.name ?: "All Albums",
modifier = Modifier.clickable { expanded = true }
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
albums.forEach { album ->
DropdownMenuItem(
text = { Text("${album.name} (${album.count})") },
onClick = {
expanded = false
onClick(album) // Select the album
}
)
}
}
}
}This slot allows you to preview currently selected media contents in a custom layout. The ImagePickerPreviewScope gives you access to:
| Property / Function | Description |
|---|---|
selectedMediaContents: List<MediaContent> |
List of selected media content |
onDeselect(mediaContent: MediaContent) |
Deselect the given media content |
Example usage:
previewTopBar = {
Row {
selectedMediaContents.forEach { media ->
AsyncImage(
model = media.uri,
contentDescription = null,
modifier = Modifier.clickable {
onDeselect(media) // Deselect
}
)
}
}
}This slot renders each image cell in the grid. Only the MediaContent is provided - the rest is up to you.
Use this slot to:
- Display thumbnails
- Indicate selected state (e.g., with a badge or overlay)
- Navigate to the preview screen
The ImagePickerCellScope gives you access to:
| Property / Function | Description |
|---|---|
mediaContent: MediaContent |
The media content represented by this cell |
onNavigateToPreviewScreen(mediaContent: MediaContent) |
Triggers navigation to the Preview Screen |
Example usage:
cellContent = {
Box(modifier = Modifier.clickable {
onNavigateToPreviewScreen(mediaContent)
}) {
AsyncImage(model = mediaContent.uri, contentDescription = null)
if (mediaContent.selected) {
Text(
text = "#${mediaContent.selectedOrder}",
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
}π‘ You can fully control the UI β whether it's adding badges, applying blur, or animating selection β by customizing this slot.
This slot allows you to define the full-screen preview UI for selected media content. The PreviewScreenScope provides:
| Property / Function | Description |
|---|---|
mediaContent: MediaContent |
The currently visible media content |
onBack() |
Navigate back to the picker screen |
onToggleSelection(mediaContent: MediaContent) |
Selects or deselects the given media content |
β οΈ Important
PreviewScreen must be explicitly declared inside ImagePickerNavHost.
If omitted, calling onNavigateToPreviewScreen() from a cell will cause a runtime crash.
The ImagePickerNavHostState stores shared selection state and picker configuration across ImagePickerScreen and PreviewScreen.
| Parameter | Description |
|---|---|
max |
Maximum number of media contents that can be selected |
autoSelectAfterCapture |
Whether to auto-select the image after camera shot |
| Property | Type | Description |
|---|---|---|
selectedMediaContents |
List<MediaContent> |
List of currently selected media contents |
You can read this state anywhere in your app to reflect selection results, UI updates, or submission logic:
val selected = state.selectedMediaContentsImagePicker is designed around three key principles:
Screens are declared inside ImagePickerNavHost { ... } just like NavHost and composable in Jetpack Navigation.
ImagePickerNavHost(state) {
ImagePickerScreen(...)
PreviewScreen { ... }
}Each UI slot receives a dedicated Scope that provides data, actions for full composability:
| Slot | Scope Interface | Description |
|---|---|---|
albumTopBar |
ImagePickerAlbumScope |
Provides album list & selection |
previewTopBar |
ImagePickerPreviewTopBarScope |
Shows selected media, toggles select |
cellContent |
ImagePickerCellScope |
Controls image cell UI & navigate to Preview Screen |
PreviewScreen |
PreviewScreenScope |
Full-screen preview screen actions |
ImagePickerNavHostState gives access to current selections and configuration.
val selectedItems = state.selectedMediaContents
val maxSelectable = state.maxMost pickers are monolithic and hard to extend. ImagePicker provides navigation-level flexibility, enabling clean separation between:
- Image list & preview
- UI customization & logic
- App-level state & picker state This structure makes it easy to build an image picker that feels native to your app, not boxed-in.





