This document is also available in Spanish: API_REFERENCE.es.md
Complete API documentation for the ImagePickerKMP library.
- API v2 vs Legacy API — Migration Guide
- rememberImagePickerKMP (Recommended)
- Main Components
- Data Classes
- Enums
- Configuration
- Platform-specific APIs
TL;DR: Use
rememberImagePickerKMPfor all new code.ImagePickerLauncherandGalleryPickerLauncherare deprecated (@Deprecated(level = WARNING)) and will be removed in a future major release. Existing code keeps working without changes.
| Feature | Legacy API (v1) |
New API (v2) ✅ Recommended |
|---|---|---|
| Camera | ImagePickerLauncher(config = ImagePickerConfig(...)) |
picker.launchCamera() |
| Gallery | GalleryPickerLauncher(onPhotosSelected = { }, ...) |
picker.launchGallery() |
| Result | Callbacks: onPhotoCaptured, onDismiss, onError |
Reactive: when (picker.result) { is Success -> } |
| State management | Manual showCamera/showGallery booleans |
Automatic via ImagePickerKMPState |
| Per-launch overrides | Not supported | Every launch*() parameter is optional override |
| Reset | Call onDismiss callback |
picker.reset() |
| Config class | ImagePickerConfig + GalleryPickerConfig |
ImagePickerKMPConfig (unified) |
Render() call needed |
❌ Legacy required wrapper composables | ❌ No Render() in new API either |
| Legacy pattern | New API equivalent |
|---|---|
var showCamera by remember { mutableStateOf(false) } |
(remove — not needed) |
showCamera = true |
picker.launchCamera() |
showGallery = true |
picker.launchGallery() |
onPhotoCaptured = { result -> ... } |
is ImagePickerResult.Success -> result.photos |
onDismiss = { showCamera = false } |
is ImagePickerResult.Dismissed -> ... |
onError = { e -> ... } |
is ImagePickerResult.Error -> result.exception |
ImagePickerConfig(cameraCaptureConfig = ...) |
ImagePickerKMPConfig(cameraCaptureConfig = ...) |
GalleryPickerConfig(includeExif = true) |
ImagePickerKMPConfig(galleryConfig = GalleryConfig(includeExif = true)) |
GalleryPickerLauncher(allowMultiple = true) |
picker.launchGallery(allowMultiple = true) |
GalleryPickerLauncher(selectionLimit = 5) |
picker.launchGallery(selectionLimit = 5) |
GalleryPickerLauncher(mimeTypes = listOf(...)) |
picker.launchGallery(mimeTypes = listOf(...)) |
Legacy — camera capture (still works, deprecated):
var showCamera by remember { mutableStateOf(false) }
if (showCamera) {
ImagePickerLauncher( // ⚠️ Deprecated
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* use result */ showCamera = false },
onDismiss = { showCamera = false },
onError = { showCamera = false }
)
)
}
Button(onClick = { showCamera = true }) { Text("Camera") }New API — camera capture (recommended):
val picker = rememberImagePickerKMP()
Button(onClick = { picker.launchCamera() }) { Text("Camera") }
when (val result = picker.result) {
is ImagePickerResult.Success -> result.first?.let { /* use photo */ }
is ImagePickerResult.Dismissed -> { /* user cancelled */ }
is ImagePickerResult.Error -> Text("Error: ${result.exception.message}")
is ImagePickerResult.Loading -> CircularProgressIndicator()
is ImagePickerResult.Idle -> { /* initial state */ }
}Legacy — gallery selection (still works, deprecated):
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
GalleryPickerLauncher( // ⚠️ Deprecated
onPhotosSelected = { photos -> selectedImages = photos; showGallery = false },
onDismiss = { showGallery = false },
onError = { showGallery = false },
allowMultiple = true,
mimeTypes = listOf(MimeType.IMAGE_JPEG)
)
}
Button(onClick = { showGallery = true }) { Text("Gallery") }New API — gallery selection (recommended):
val picker = rememberImagePickerKMP(
config = ImagePickerKMPConfig(
galleryConfig = GalleryConfig(allowMultiple = true, selectionLimit = 10)
)
)
Button(onClick = { picker.launchGallery() }) { Text("Gallery") }
// or with per-launch override:
Button(onClick = { picker.launchGallery(allowMultiple = true, selectionLimit = 5) }) { Text("Gallery (5 max)") }
when (val result = picker.result) {
is ImagePickerResult.Success -> result.photos.forEach { /* use each photo */ }
else -> { /* handle other states */ }
}Note — internal architecture:
rememberImagePickerKMPcallsImagePickerLauncher/GalleryPickerLauncherinternally as platform-specific rendering layers. The internal call site uses@Suppress("DEPRECATION")so consumers of the new API see no compiler warnings. Only developers who call the legacy functions directly see the migration warning.
Available since:
1.0.35-alpha1· All platforms
rememberImagePickerKMP es el punto de entrada recomendado para Compose. Retorna un ImagePickerKMPState — un state holder estable que reemplaza los booleans manuales showCamera/showGallery. No requiere ningún Render() ni composable adicional — el picker se auto-gestiona al invocar launchCamera() o launchGallery().
@Composable
fun rememberImagePickerKMP(
config: ImagePickerKMPConfig = ImagePickerKMPConfig()
): ImagePickerKMPStatedata class ImagePickerKMPConfig(
val cameraCaptureConfig: CameraCaptureConfig = CameraCaptureConfig(),
val galleryConfig: GalleryConfig = GalleryConfig(),
val enableCrop: Boolean = false,
val cropConfig: CropConfig = CropConfig(),
val uiConfig: UiConfig = UiConfig(),
val permissionAndConfirmationConfig: PermissionAndConfirmationConfig = PermissionAndConfirmationConfig()
)| Property | Type | Default | Description |
|---|---|---|---|
cameraCaptureConfig |
CameraCaptureConfig |
defaults | Camera behaviour, compression, EXIF, UI styling |
galleryConfig |
GalleryConfig |
defaults | Multi-select, MIME types, selection limit, EXIF |
enableCrop |
Boolean |
false |
Show crop UI after every capture / selection |
cropConfig |
CropConfig |
defaults | Crop shape, aspect ratio, freeform |
uiConfig |
UiConfig |
defaults | Custom button colors and icons |
permissionAndConfirmationConfig |
PermissionAndConfirmationConfig |
defaults | Custom permission dialogs and confirmation screen |
| Method / Property | Description |
|---|---|
result: ImagePickerResult |
Estado reactivo. Empieza en Idle. Observar con when. Se actualiza automáticamente cuando el picker abre, el usuario selecciona o cancela. |
launchCamera(cameraCaptureConfig?, enableCrop?, onDismiss?, onError?) |
Abre la cámara. Todos los parámetros son opcionales y sobreescriben el config global solo para este lanzamiento. |
launchGallery(allowMultiple?, mimeTypes?, selectionLimit?, enableCrop?, includeExif?, redactGpsData?, mimeTypeMismatchMessage?, cameraCaptureConfig?, onDismiss?, onError?) |
Abre la galería. Todos los parámetros son opcionales y sobreescriben el GalleryConfig global solo para este lanzamiento. |
reset() |
Resetea result a Idle y cierra cualquier picker activo. |
⚠️ No existeRender()nilaunchPicker()en esta API. El picker se gestiona internamente.
sealed class ImagePickerResult {
data object Idle : ImagePickerResult() // Estado inicial / tras reset()
data object Loading : ImagePickerResult() // Picker abierto, esperando acción
data class Success(val photos: List<PhotoResult>) : ImagePickerResult()
data object Dismissed : ImagePickerResult() // Usuario cerró sin seleccionar
data class Error(val exception: Exception) : ImagePickerResult()
}Success also exposes val first: PhotoResult? — primera foto (útil en captura de cámara).
@Composable
fun MyScreen(innerPadding: PaddingValues) {
// No se necesita Render() ni booleans manuales
val picker = rememberImagePickerKMP(
config = ImagePickerKMPConfig(
enableCrop = false,
galleryConfig = GalleryConfig(
allowMultiple = true,
selectionLimit = 10,
includeExif = true,
redactGpsData = true,
mimeTypes = listOf(MimeType.IMAGE_JPEG)
)
)
)
val result = picker.result
Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) {
Box(modifier = Modifier.fillMaxWidth().weight(1f), contentAlignment = Alignment.Center) {
when (result) {
is ImagePickerResult.Loading -> CircularProgressIndicator()
is ImagePickerResult.Success -> {
val photos = result.photos
if (photos.size == 1) {
photos.first().loadPainter()?.let { Image(it, contentDescription = null) }
} else {
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(photos) { photo ->
photo.loadPainter()?.let { Image(it, contentDescription = null) }
}
}
}
}
is ImagePickerResult.Error -> Text("Error: ${result.exception.message}")
is ImagePickerResult.Dismissed,
is ImagePickerResult.Idle -> Text("No image selected")
}
}
Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Button(onClick = { picker.launchCamera() }, modifier = Modifier.weight(1f)) {
Text("Cámara")
}
Button(onClick = { picker.launchGallery() }, modifier = Modifier.weight(1f)) {
Text("Galería")
}
}
}
}// Override solo para este botón — no afecta el config global
Button(onClick = {
picker.launchGallery(
allowMultiple = true,
mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG),
selectionLimit = 5,
includeExif = true
)
}) { Text("Pick up to 5 images") }
// Cámara con compresión HIGH y crop, solo para este tap
Button(onClick = {
picker.launchCamera(
cameraCaptureConfig = CameraCaptureConfig(compressionLevel = CompressionLevel.HIGH),
enableCrop = true
)
}) { Text("Camera HD") }The photo capture functionality in ImagePickerKMP allows developers to integrate a modern, customizable, and cross-platform camera experience into their applications. It includes flash control, camera switching, preview, confirmation, and complete UI customization.
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// Handle the captured photo result
println("Photo captured: ${result.uri}")
},
onError = { exception ->
// Handle errors
println("Error: ${exception.message}")
},
onDismiss = {
// Handle when user cancels
println("User cancelled")
}
)
)ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// Handle the captured photo result
cameraPhoto = result
showCameraPicker = false
},
onError = { exception ->
// Handle errors
showCameraPicker = false
},
onDismiss = {
// Handle cancellation
showCameraPicker = false
},
// Custom confirmation view (Android only)
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customConfirmationView = { photoResult, onConfirm, onRetry ->
MyCustomConfirmationView(
result = photoResult,
onConfirm = onConfirm,
onRetry = onRetry
)
}
)
)
)
)ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// Photo is captured automatically without confirmation
cameraPhoto = result
showCameraPicker = false
},
onError = { exception ->
// Handle errors
showCameraPicker = false
},
onDismiss = {
// Handle cancellation
showCameraPicker = false
},
// Configuration to skip confirmation
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
skipConfirmation = true // NEW! Skips the confirmation screen
)
)
)
)- onPhotoCaptured: Callback with the photo result (
CameraPhotoHandler.PhotoResult) - onError: Callback to handle errors (
Exception) - onDismiss: Callback when user cancels
- preference: Photo quality preference (
CapturePhotoPreference.FAST,BALANCED,QUALITY) - customConfirmationView: Composable to customize confirmation UI (Android only)
- customPickerDialog: Composable to customize selection dialog (iOS only)
- Preview: User sees the camera in real-time.
- Flash control: Button to toggle between Auto, On, Off (visual icons).
- Camera switching: Button to toggle between rear and front camera.
- Capture: Central button to take the photo.
- Confirmation: Modern view to accept or retry the photo, with customizable texts and icons.
- The permission system is managed automatically.
- You can completely customize the confirmation UI.
- Default texts are in English, but you can easily localize them.
- Flash only works in
BALANCEDorQUALITYquality modes. - If you need even more control, implement your own
customConfirmationView.
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/CameraCapturePreview.kt: Preview logic and camera controls.
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/ImageConfirmationViewWithCustomButtons.kt: Photo confirmation UI.
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/CameraController.kt: Camera control logic, flash and camera switching.
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/CameraXManager.kt: Abstraction for camera management.
The gallery functionality in ImagePickerKMP allows developers to integrate a modern and customizable experience for selecting images from the device gallery. It supports single or multiple selection, file type filters, and custom confirmation.
GalleryPickerLauncher(
onPhotosSelected = { results ->
// Handle selected images
println("Selected images: ${results}")
},
onError = { exception ->
// Handle errors
println("Error: ${exception.message}")
},
onDismiss = {
// Handle when user cancels
println("User cancelled selection")
}
)GalleryPickerLauncher(
onPhotosSelected = { results ->
// Handle multiple selected images
selectedImages = results
showGalleryPicker = false
},
onError = { exception ->
// Handle errors
showGalleryPicker = false
},
onDismiss = {
// Handle cancellation
showGalleryPicker = false
},
allowMultiple = true, // Allow multiple selection
mimeTypes = listOf("image/jpeg", "image/png"), // Filter by file types
selectionLimit = 30 // Selection limit (maximum 30 images) iOS only, Android has no limit
)- onPhotosSelected: Callback with the list of selected images (
List<GalleryPhotoHandler.PhotoResult>) - onError: Callback to handle errors (
Exception) - onDismiss: Callback when user cancels
- allowMultiple: Allows selecting multiple images (default:
false) - mimeTypes: List of allowed MIME types (default:
listOf("image/*")) - selectionLimit: Maximum selection limit (default:
30)
- Gallery selector: User can choose one image or several (if
allowMultipleis enabled) - Filters: File type filters can be applied using
mimeTypes - Limits: A maximum selection limit can be set with
selectionLimit
- The permission system is managed automatically
- Multiple selection is supported on both platforms (Android and iOS)
- MIME types allow filtering by specific image formats
- Selection limit helps control application performance
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/GalleryPickerLauncher.android.kt: Image selection logic on Android.
- library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/GalleryPickerLauncher.kt: Cross-platform abstraction for gallery.
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/ImageConfirmationViewWithCustomButtons.kt: Image confirmation UI.
The image compression functionality in ImagePickerKMP automatically optimizes image size while maintaining acceptable quality. It works for both camera capture and gallery selection, with configurable compression levels and async processing.
- Automatic compression: Applies compression transparently during image processing
- Configurable levels: LOW, MEDIUM, HIGH compression options
- Multi-format support: JPEG, PNG, HEIC, HEIF, WebP, GIF, BMP
- Async processing: Non-blocking UI with Kotlin Coroutines
- Smart optimization: Combines dimension scaling + quality compression
- Memory efficient: Proper bitmap recycling and cleanup
| Level | Quality | Max Dimension | Use Case |
|---|---|---|---|
| LOW | 95% | 2560px | High-quality sharing, professional use |
| MEDIUM | 75% | 1920px | Recommended - Social media, general use |
| HIGH | 50% | 1280px | Storage optimization, thumbnails |
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
val fileSizeKB = (result.fileSize ?: 0) / 1024.0
println("Compressed image size: ${String.format("%.2f", fileSizeKB)}KB")
println("Exact size: ${result.fileSize} bytes")
},
onError = { exception ->
println("Error: ${exception.message}")
},
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.MEDIUM
)
)
)GalleryPickerLauncher(
onPhotosSelected = { results ->
results.forEach { photo ->
val fileSizeKB = (photo.fileSize ?: 0) / 1024.0
println("Original: ${photo.fileName}")
println("Compressed size: ${String.format("%.2f", fileSizeKB)}KB")
println("Exact size: ${photo.fileSize} bytes")
}
},
onError = { exception ->
println("Error: ${exception.message}")
},
allowMultiple = true,
mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG),
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = CompressionLevel.HIGH // Optimize for storage
)
)- Image Loading: Original image is loaded from camera/gallery
- Dimension Scaling: Image is resized if larger than max dimension
- Quality Compression: JPEG compression is applied based on level
- Temporary File: Compressed image is saved to app cache
- Result Delivery: New URI with compressed image is returned
| Platform | Camera Compression | Gallery Compression | Async Processing |
|---|---|---|---|
| Android | ✅ | ✅ | ✅ Coroutines |
| iOS | ✅ | ✅ | ✅ Coroutines |
- Memory Usage: Original bitmaps are recycled after compression
- Processing Time: Runs on background threads (Dispatchers.IO)
- Storage: Compressed images are stored in app cache directory
- Quality: Smart balance between file size and visual quality
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/data/processors/ImageProcessor.kt: Camera compression logic
- library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/GalleryPickerLauncher.android.kt: Gallery compression implementation
- library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/models/CompressionLevel.kt: Compression level definitions
⚠️ Deprecated —ImagePickerLauncheris marked@Deprecated(level = WARNING). It still compiles and runs normally, but the compiler will show a migration warning. Migrate torememberImagePickerKMPfor new code.
Main composable for launching the image picker.
@Deprecated("Use rememberImagePickerKMP() instead.")
@Composable
expect fun ImagePickerLauncher(
config: ImagePickerConfig
)config: ImagePickerConfig- Complete image picker configuration
⚠️ Deprecated —GalleryPickerLauncheris marked@Deprecated(level = WARNING). It still compiles and runs normally, but the compiler will show a migration warning. Migrate torememberImagePickerKMPfor new code.
Composable for selecting images from gallery with intelligent picker selection for Android.
@Composable
expect fun GalleryPickerLauncher(
config: GalleryPickerConfig = GalleryPickerConfig(),
onPhotosSelected: (List<PhotoResult>) -> Unit,
onError: (Exception) -> Unit,
onDismiss: () -> Unit = {},
allowMultiple: Boolean = false,
mimeTypes: List<MimeType> = listOf(MimeType.IMAGE_ALL),
selectionLimit: Long = SELECTION_LIMIT
)config- Configuration for gallery picker behavior and Android-specific settingsonPhotosSelected- Callback with the list of selected imagesonError- Callback to handle errorsonDismiss- Callback when user cancelsallowMultiple- Allows multiple selection (default:false)mimeTypes- List of allowed MIME typesselectionLimit- Maximum selection limit
The GalleryPickerLauncher automatically chooses the appropriate picker based on requested MIME types:
- Images only (
image/*): Opens native Android gallery usingMediaStore - PDFs included (
application/pdf): Opens file explorer for document access - Mixed types: Uses file explorer for maximum compatibility
This ensures users get the expected interface for their content type without configuration.
GalleryPickerLauncher(
config = GalleryPickerConfig(
includeExif = true,
androidGalleryConfig = AndroidGalleryConfig(
forceGalleryOnly = false,
localOnly = true
)
),
mimeTypes = listOf(MimeType.APPLICATION_PDF),
onPhotosSelected = { results ->
// Handle selected files
}
)Composable for managing camera permissions.
@Composable
expect fun RequestCameraPermission(
dialogConfig: CameraPermissionDialogConfig,
onPermissionPermanentlyDenied: () -> Unit,
onResult: (Boolean) -> Unit,
customPermissionHandler: (() -> Unit)?
)dialogConfig: CameraPermissionDialogConfig- Permission dialog configurationonPermissionPermanentlyDenied: () -> Unit- Callback when permission is permanently deniedonResult: (Boolean) -> Unit- Callback with permission resultcustomPermissionHandler: (() -> Unit)?- Custom permission handler
Configuration specific to Android gallery picker behavior.
data class AndroidGalleryConfig(
val forceGalleryOnly: Boolean = true,
val localOnly: Boolean = true
) {
companion object {
fun forMimeTypes(mimeTypes: List<MimeType>): AndroidGalleryConfig
fun forMimeTypeStrings(mimeTypes: List<String>): AndroidGalleryConfig
}
}-
forceGalleryOnly- Forces gallery vs file explorer usagetrue: UsesIntent.ACTION_PICK+MediaStore(opens native gallery)false: UsesActivityResultContracts.GetContent()(may open file explorer)- Default:
true, but automatically adjusted based on MIME types
-
localOnly- Include only local imagestrue: AddsEXTRA_LOCAL_ONLYto intent (no cloud storage)false: Allows images from cloud storage- Default:
true
// Automatic configuration based on MIME types
val autoConfig = AndroidGalleryConfig.forMimeTypes(listOf(MimeType.APPLICATION_PDF))
// Result: forceGalleryOnly = false (uses file explorer for PDFs)
// Manual configuration
GalleryPickerLauncher(
// ... other parameters ...
androidGalleryConfig = AndroidGalleryConfig(
forceGalleryOnly = false, // Force file explorer
localOnly = true
)
)| Detected MIME Types | forceGalleryOnly |
Result |
|---|---|---|
Images only (image/*) |
true |
Native gallery |
Contains application/pdf |
false |
File explorer |
| Mixed types (image + others) | false |
File explorer |
| Non-image types | false |
File explorer |
Represents the result of a photo capture from camera.
data class PhotoResult(
val uri: String,
val width: Int,
val height: Int,
val fileName: String? = null,
val fileSize: Long? = null,
val mimeType: String? = null,
val exif: ExifData? = null // EXIF metadata (Android/iOS only)
)File Path Access
// Convert to kotlinx.io Path for cross-platform file operations (v1.0.38+)
val path: Path? = photoResult.toPath()
// Get absolute file system path as String (v1.0.40+)
val absolutePath: String? = photoResult.absolutePathExtensions:
fun PhotoResult.toPath(): Path?— (Available since v1.0.38) Converts the photo's URI to akotlinx.io.files.Pathfor cross-platform file operations. Returnsnullif conversion fails. Requireskotlinx-iodependency.val PhotoResult.absolutePath: String?— (Available since v1.0.40) Returns the absolute file system path as a String. Platform-specific implementation (Android uses ContentResolver, iOS uses URL.path, Desktop/Web use direct path extraction).
Example usage:
val picker = rememberImagePickerKMP()
when (val result = picker.result) {
is ImagePickerResult.Success -> {
result.first?.let { photo ->
// Using kotlinx.io Path (cross-platform, v1.0.38+)
photo.toPath()?.let { path ->
println("File path: $path")
// Use kotlinx-io file operations
}
// Using absolute path String (platform-specific, v1.0.40+)
photo.absolutePath?.let { path ->
println("Absolute path: $path")
}
}
}
else -> {}
}Represents the result of an image selected from gallery.
data class PhotoResult(
val uri: String,
val width: Int,
val height: Int,
val fileName: String? = null,
val fileSize: Long? = null,
val mimeType: String? = null,
val exif: ExifData? = null
)Main configuration for the image picker.
data class ImagePickerConfig(
val onPhotoCaptured: (CameraPhotoHandler.PhotoResult) -> Unit,
val onPhotosSelected: ((List<GalleryPhotoHandler.PhotoResult>) -> Unit)? = null,
val onError: (Exception) -> Unit,
val onDismiss: () -> Unit = {},
val dialogTitle: String = "Select option",
val takePhotoText: String = "Take photo",
val selectFromGalleryText: String = "Select from gallery",
val cancelText: String = "Cancel",
val customPickerDialog: (@Composable (
onTakePhoto: () -> Unit,
onSelectFromGallery: () -> Unit,
onCancel: () -> Unit
) -> Unit)? = null,
val cameraCaptureConfig: CameraCaptureConfig = CameraCaptureConfig()
)Configuration for camera capture.
data class CameraCaptureConfig(
val preference: CapturePhotoPreference = CapturePhotoPreference.BALANCED,
val captureButtonSize: Dp = 72.dp,
val compressionLevel: CompressionLevel? = CompressionLevel.MEDIUM,
val includeExif: Boolean = false,
val redactGpsData: Boolean = true,
val uiConfig: UiConfig = UiConfig(),
val cameraCallbacks: CameraCallbacks = CameraCallbacks(),
val permissionAndConfirmationConfig: PermissionAndConfirmationConfig = PermissionAndConfirmationConfig(),
val cropConfig: CropConfig = CropConfig(),
val cameraScaleType: CameraScaleType = CameraScaleType.FILL_CENTER // NEW in v1.0.41
)Parameters:
preference- Photo capture quality preferencecaptureButtonSize- Size of the capture buttoncompressionLevel- Automatic image compression level (null= disabled,MEDIUM= recommended)includeExif- Extract EXIF metadata including GPS, camera model, timestamps (Android/iOS only)redactGpsData- Strip GPS coordinates from EXIF before delivery (defaulttrue). Effective only whenincludeExif = trueuiConfig- UI customization configurationcameraCallbacks- Camera lifecycle callbackspermissionAndConfirmationConfig- Permission and confirmation dialogscropConfig- Interactive crop UI configurationcameraScaleType- NEW in v1.0.41 How the camera preview is scaled inside its viewport (Android only). Defaults toCameraScaleType.FILL_CENTER
Camera scale type examples:
// Default — fills the viewport, crops the feed to fit
CameraCaptureConfig()
// Letterbox — full camera feed visible, matches captured image framing
CameraCaptureConfig(
cameraScaleType = CameraScaleType.FIT_CENTER
)
// Fill from top-left
CameraCaptureConfig(
cameraScaleType = CameraScaleType.FILL_START
)Configuration for permissions and post-capture confirmation screen.
data class PermissionAndConfirmationConfig(
val customPermissionHandler: ((PermissionConfig) -> Unit)? = null,
val customConfirmationView: (@Composable (PhotoResult, (PhotoResult) -> Unit, () -> Unit) -> Unit)? = null,
val customDeniedDialog: (@Composable (onRetry: () -> Unit, onDismiss: () -> Unit) -> Unit)? = null,
val customSettingsDialog: (@Composable (onOpenSettings: () -> Unit, onDismiss: () -> Unit) -> Unit)? = null,
val skipConfirmation: Boolean = false,
val cancelButtonTextIOS: String? = "Cancel",
val onCancelPermissionConfigIOS: (() -> Unit)? = null,
val confirmationImageContentScale: ContentScale = ContentScale.Crop // NEW in v1.0.41
)customPermissionHandler: ((PermissionConfig) -> Unit)?- Custom permission handler for text-based customizationcustomConfirmationView: (@Composable (...) -> Unit)?- Custom composable for photo confirmationcustomDeniedDialog: (@Composable (...) -> Unit)?- Custom composable dialog when permission is denied. Always callonRetryoronDismisson user interactioncustomSettingsDialog: (@Composable (...) -> Unit)?- Custom composable dialog for opening system settings. Always callonOpenSettingsoronDismisson user interactionskipConfirmation: Boolean- Iftrue, delivers the captured photo directly without showing the confirmation screen (Android)cancelButtonTextIOS: String?- Text label for the cancel button in the iOS permission alert. Defaults to"Cancel"onCancelPermissionConfigIOS: (() -> Unit)?- Callback invoked when the user taps cancel in the iOS permission alertconfirmationImageContentScale: ContentScale- NEW in v1.0.41 How the captured photo is scaled in the post-capture confirmation preview. Accepts any ComposeContentScalevalue. Defaults toContentScale.Crop
Confirmation image scale examples:
// Default — crop to fill the preview area
PermissionAndConfirmationConfig()
// Fit — show the entire photo letterboxed
PermissionAndConfirmationConfig(
confirmationImageContentScale = ContentScale.Fit
)
// Fill width
PermissionAndConfirmationConfig(
confirmationImageContentScale = ContentScale.FillWidth
)Configuration for user interface styling.
data class UiConfig(
val buttonColor: Color? = null,
val iconColor: Color? = null,
val buttonSize: Dp? = null,
val flashIcon: ImageVector? = null,
val switchCameraIcon: ImageVector? = null,
val galleryIcon: ImageVector? = null
)Configuration for camera callbacks.
data class CameraCallbacks(
val onCameraReady: (() -> Unit)? = null,
val onCameraSwitch: (() -> Unit)? = null,
val onPermissionError: ((Exception) -> Unit)? = null,
val onGalleryOpened: (() -> Unit)? = null
)Contains comprehensive EXIF metadata extracted from images. Available on Android and iOS only.
data class ExifData(
val latitude: Double? = null,
val longitude: Double? = null,
val altitude: Double? = null,
val dateTaken: String? = null,
val dateTime: String? = null,
val digitizedTime: String? = null,
val originalTime: String? = null,
val cameraModel: String? = null,
val cameraManufacturer: String? = null,
val software: String? = null,
val owner: String? = null,
val orientation: String? = null,
val colorSpace: String? = null,
val whiteBalance: String? = null,
val flash: String? = null,
val focalLength: String? = null,
val aperture: String? = null,
val shutterSpeed: String? = null,
val iso: String? = null,
val imageWidth: Int? = null,
val imageHeight: Int? = null
)Example Usage:
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result ->
result.exif?.let { exif ->
println(" GPS: ${exif.latitude}, ${exif.longitude}")
println(" Camera: ${exif.cameraModel}")
println(" Date: ${exif.dateTaken}")
println(" Settings: ISO ${exif.iso}, f/${exif.aperture}")
}
},
cameraCaptureConfig = CameraCaptureConfig(
includeExif = true
)
)
)
GalleryPickerLauncher(
onPhotosSelected = { results ->
results.forEachIndexed { index, result ->
println("Image $index:")
result.exif?.let { exif ->
println(" Location: ${exif.latitude}, ${exif.longitude}")
println(" Camera: ${exif.cameraModel}")
println(" Date: ${exif.dateTaken}")
} ?: println(" No EXIF data available")
}
},
allowMultiple = true,
includeExif = true
)Platform Support:
- ✅ Android: Full support via
androidx.exifinterface - ✅ iOS: Full support via native ImageIO framework
- ❌ Desktop/Web/Wasm: Not supported (returns null)
Configuration for image gallery.
data class GalleryConfig(
val allowMultiple: Boolean = false,
val mimeTypes: List<String> = listOf("image/*"),
val selectionLimit: Int = 30
)Configuration for camera preview and callbacks.
data class CameraPreviewConfig(
val captureButtonSize: Dp = 72.dp,
val uiConfig: UiConfig = UiConfig(),
val cameraCallbacks: CameraCallbacks = CameraCallbacks()
)captureButtonSize: Dp- Size of capture button (default 72.dp)uiConfig: UiConfig- User interface configurationcameraCallbacks: CameraCallbacks- Camera callbacks
val cameraPreviewConfig = CameraPreviewConfig(
captureButtonSize = 80.dp,
uiConfig = UiConfig(
buttonColor = Color.Blue,
iconColor = Color.White
),
cameraCallbacks = CameraCallbacks(
onCameraReady = { println("Camera ready") },
onCameraSwitch = { println("Camera switched") }
)
)Configuration for camera permission dialogs.
data class CameraPermissionDialogConfig(
val titleDialogConfig: String,
val descriptionDialogConfig: String,
val btnDialogConfig: String,
val titleDialogDenied: String,
val descriptionDialogDenied: String,
val btnDialogDenied: String,
val customDeniedDialog: @Composable ((onRetry: () -> Unit) -> Unit)? = null,
val customSettingsDialog: @Composable ((onOpenSettings: () -> Unit) -> Unit)? = null
)titleDialogConfig: String- Title for configuration dialogdescriptionDialogConfig: String- Description for configuration dialogbtnDialogConfig: String- Button text for configuration dialogtitleDialogDenied: String- Title for denial dialogdescriptionDialogDenied: String- Description for denial dialogbtnDialogDenied: String- Button text for denial dialogcustomDeniedDialog: @Composable ((onRetry: () -> Unit) -> Unit)?- Custom dialog for retrycustomSettingsDialog: @Composable ((onOpenSettings: () -> Unit) -> Unit)?- Custom dialog for settings
val dialogConfig = CameraPermissionDialogConfig(
titleDialogConfig = "Camera permission required",
descriptionDialogConfig = "Camera permission is required to capture photos. Please grant it in settings",
btnDialogConfig = "Open settings",
titleDialogDenied = "Camera permission denied",
descriptionDialogDenied = "Camera permission is required to capture photos. Please grant the permissions",
btnDialogDenied = "Grant permission"
)Configuration for permission dialogs.
data class PermissionConfig(
val titleDialogConfig: String = "Camera permission required",
val descriptionDialogConfig: String = "Camera permission is required to capture photos. Please grant it in settings",
val btnDialogConfig: String = "Open settings",
val titleDialogDenied: String = "Camera permission denied",
val descriptionDialogDenied: String = "Camera permission is required to capture photos. Please grant the permissions",
val btnDialogDenied: String = "Grant permission"
)Interface for logging messages within the ImagePicker library.
interface ImagePickerLogger {
fun log(message: String)
}object DefaultLogger : ImagePickerLogger {
override fun log(message: String) {
println(message)
}
}class CustomLogger : ImagePickerLogger {
override fun log(message: String) {
Log.d("ImagePicker", message)
}
}Constants for ImagePicker user interface.
object ImagePickerUiConstants {
const val ORIENTATION_ROTATE_90 = 90f
const val ORIENTATION_ROTATE_180 = 180f
const val ORIENTATION_ROTATE_270 = 270f
const val ORIENTATION_FLIP_HORIZONTAL_X = -1f
const val ORIENTATION_FLIP_HORIZONTAL_Y = 1f
const val ORIENTATION_FLIP_VERTICAL_X = 1f
const val ORIENTATION_FLIP_VERTICAL_Y = -1f
const val SYSTEM_VERSION_10 = 10.0
const val DELAY_TO_TAKE_PHOTO = 60L
const val SELECTION_LIMIT = 30L
}Composable for handling camera permissions.
@Composable
expect fun RequestCameraPermission(
dialogConfig: CameraPermissionDialogConfig,
onPermissionPermanentlyDenied: () -> Unit,
onResult: (Boolean) -> Unit,
customPermissionHandler: (() -> Unit)?
)dialogConfig: CameraPermissionDialogConfig- Permission dialog configurationonPermissionPermanentlyDenied: () -> Unit- Callback when permission is permanently deniedonResult: (Boolean) -> Unit- Callback with permission resultcustomPermissionHandler: (() -> Unit)?- Custom permission handler
@Composable
fun CustomPermissionHandler() {
val dialogConfig = CameraPermissionDialogConfig(
titleDialogConfig = "Camera permission required",
descriptionDialogConfig = "Please enable camera access in settings",
btnDialogConfig = "Open settings",
titleDialogDenied = "Permission denied",
descriptionDialogDenied = "Camera permission is required",
btnDialogDenied = "Grant permission"
)
RequestCameraPermission(
dialogConfig = dialogConfig,
onPermissionPermanentlyDenied = {
println("Permission permanently denied")
},
onResult = { granted ->
println("Permission granted: $granted")
},
customPermissionHandler = null
)
}Exception thrown when an error occurs during photo capture or processing.
class PhotoCaptureException(message: String) : Exception(message)Used to signal camera failures or image processing in the ImagePicker library.
@Composable
fun ErrorHandlingExample() {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception ->
when (exception) {
is PhotoCaptureException -> {
println("Photo capture error: ${exception.message}")
}
else -> {
println("Unknown error: ${exception.message}")
}
}
}
)
)
}try {
capturePhoto()
} catch (e: PhotoCaptureException) {
println("Error capturing photo: ${e.message}")
}Base exception for ImagePicker library errors.
open class ImagePickerException(message: String) : Exception(message)Exception thrown when permissions are denied.
class PermissionDeniedException(message: String) : ImagePickerException(message)try {
requestCameraPermission()
} catch (e: PermissionDeniedException) {
println("Permissions denied: ${e.message}")
}NEW in v1.0.41
Controls how the camera preview is scaled inside its viewport on Android. Mirrors androidx.camera.view.PreviewView.ScaleType.
enum class CameraScaleType {
FILL_CENTER, // Fill viewport, center-crop any overflow (default)
FILL_START, // Fill viewport, align to top-left, crop overflow
FILL_END, // Fill viewport, align to bottom-right, crop overflow
FIT_CENTER, // Letterbox — entire feed visible, centered
FIT_START, // Letterbox — entire feed visible, aligned to top-left
FIT_END // Letterbox — entire feed visible, aligned to bottom-right
}Notes:
FILL_*values crop the camera feed to fill the entire viewport. The visible viewfinder area may be narrower than the captured image.FIT_*values show the entire camera feed with letterboxing. The viewfinder framing matches the captured image exactly.- Currently applied on Android only. iOS uses the system camera UI where preview and capture framing already match.
- Used in
CameraCaptureConfig.cameraScaleType. Defaults toFILL_CENTER.
Represents different compression levels for image processing.
enum class CompressionLevel {
LOW, // Low compression - maintains high quality but larger file size (95% quality, 2560px max)
MEDIUM, // Medium compression - balanced quality and file size (75% quality, 1920px max)
HIGH // High compression - smaller file size but lower quality (50% quality, 1280px max)
}Quality Mapping:
LOW: 95% quality, maximum dimension 2560px - Best for high-quality sharingMEDIUM: 75% quality, maximum dimension 1920px - Recommended for most use casesHIGH: 50% quality, maximum dimension 1280px - Best for storage optimization
Represents photo capture preferences.
enum class CapturePhotoPreference {
FAST, // Fast capture with lower quality
BALANCED, // Balance between speed and quality
QUALITY // Maximum quality (slower)
}String resources used in the library.
enum class StringResource {
CAMERA_PERMISSION_REQUIRED,
CAMERA_PERMISSION_DESCRIPTION,
OPEN_SETTINGS,
CAMERA_PERMISSION_DENIED,
CAMERA_PERMISSION_DENIED_DESCRIPTION,
GRANT_PERMISSION,
CAMERA_PERMISSION_PERMANENTLY_DENIED,
IMAGE_CONFIRMATION_TITLE,
ACCEPT_BUTTON,
RETRY_BUTTON,
SELECT_OPTION_DIALOG_TITLE,
TAKE_PHOTO_OPTION,
SELECT_FROM_GALLERY_OPTION,
CANCEL_OPTION,
PREVIEW_IMAGE_DESCRIPTION,
HD_QUALITY_DESCRIPTION,
SD_QUALITY_DESCRIPTION,
INVALID_CONTEXT_ERROR,
PHOTO_CAPTURE_ERROR,
GALLERY_SELECTION_ERROR,
PERMISSION_ERROR,
GALLERY_PERMISSION_REQUIRED,
GALLERY_PERMISSION_DESCRIPTION,
GALLERY_PERMISSION_DENIED,
GALLERY_PERMISSION_DENIED_DESCRIPTION,
GALLERY_GRANT_PERMISSION,
GALLERY_BTN_SETTINGS
}Represents a specific exception from ImagePickerKMP.
class ImagePickerException(message: String) : Exception(message)Main configuration class for launching the image picker.
data class ImagePickerConfig(
val onPhotoCaptured: (CameraPhotoHandler.PhotoResult) -> Unit,
val onPhotosSelected: ((List<GalleryPhotoHandler.PhotoResult>) -> Unit)? = null,
val onError: (Exception) -> Unit,
val onDismiss: () -> Unit = {},
val dialogTitle: String = "Select option",
val takePhotoText: String = "Take photo",
val selectFromGalleryText: String = "Select from gallery",
val cancelText: String = "Cancel",
val customPickerDialog: (
@Composable (
onTakePhoto: () -> Unit,
onSelectFromGallery: () -> Unit,
onCancel: () -> Unit
) -> Unit
)? = null,
val cameraCaptureConfig: CameraCaptureConfig = CameraCaptureConfig()
)onPhotoCaptured: (CameraPhotoHandler.PhotoResult) -> Unit- Callback when a photo is capturedonPhotosSelected: ((List<GalleryPhotoHandler.PhotoResult>) -> Unit)?- Callback for multiple gallery selectiononError: (Exception) -> Unit- Callback when an error occursonDismiss: () -> Unit- Callback when picker is closeddialogTitle: String- Selection dialog titletakePhotoText: String- Text for camera optionselectFromGalleryText: String- Text for gallery optioncancelText: String- Text for cancelcustomPickerDialog: (@Composable (...) -> Unit)?- Custom dialog for iOScameraCaptureConfig: CameraCaptureConfig- Camera capture configuration
val config = ImagePickerConfig(
onPhotoCaptured = { result ->
println("Photo captured: ${result.uri}")
},
onPhotosSelected = { results ->
println("${results.size} photos selected")
},
onError = { exception ->
println("Error: ${exception.message}")
},
onDismiss = {
println("Picker closed")
},
dialogTitle = "Select option", // Title for the dialog for iOS
takePhotoText = "Take photo", // Text for the camera option for iOS
selectFromGalleryText = "Select from gallery", // Text for the gallery option for iOS
cancelText = "Cancel", // Text for cancel for iOS
// Custom dialog for iOS
customPickerDialog = { onTakePhoto, onSelectFromGallery, onCancel ->
Column {
Button(onClick = onTakePhoto) {
Text("Camera")
}
Button(onClick = onSelectFromGallery) {
Text("Gallery")
}
Button(onClick = onCancel) {
Text("Cancel")
}
}
},
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.HIGH_QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customConfirmationView = { photoResult, onConfirm, onRetry ->
// Custom confirmation view
}
)
)
)The main composable function for launching the image picker.
@Composable
fun ImagePickerLauncher(
onPhotoCaptured: (PhotoResult) -> Unit,
onError: (Exception) -> Unit,
onDismiss: () -> Unit = {},
customPermissionHandler: ((PermissionConfig) -> Unit)? = null,
customConfirmationView: (@Composable (PhotoResult, (PhotoResult) -> Unit, () -> Unit) -> Unit)? = null,
preference: CapturePhotoPreference? = null
)onPhotoCaptured: (PhotoResult) -> Unit- Callback when a photo is capturedonError: (Exception) -> Unit- Callback when an error occurscustomPermissionHandler: ((PermissionConfig) -> Unit)?- Custom permission handlingcustomConfirmationView: (@Composable (PhotoResult, (PhotoResult) -> Unit, () -> Unit) -> Unit)?- Custom confirmation viewpreference: CapturePhotoPreference?- Photo capture preferences