This document is also available in English: FAQ.md
Preguntas y respuestas comunes sobre ImagePickerKMP.
ImagePickerKMP es una librería moderna y multiplataforma para selección de imágenes en Kotlin Multiplatform (KMP) que proporciona integración de cámara para Android e iOS.
Características clave:
-
Integración de cámara multiplataforma
-
Manejo inteligente de permisos
-
Componentes de UI personalizables
-
Captura de fotos de alta calidad
-
Manejo de errores completo
- Android: API 21+ (Android 5.0+)
- iOS: iOS 12.0+
- Kotlin Multiplatform: Soporte completo
Android:
- SDK mínimo: API 21
- Kotlin: 1.8+
- Compose Multiplatform: 1.4+
iOS:
- iOS: 12.0+
- Xcode: 14+
- Kotlin Multiplatform: 1.8+
Sí, ImagePickerKMP es open-source y gratuita bajo la licencia MIT. Puedes usarla en proyectos personales y comerciales.
Ventajas:
-
Multiplataforma con un solo código base
-
UI moderna con Compose Multiplatform
-
Manejo inteligente de permisos
-
Componentes personalizables
-
Desarrollo y soporte activo
Comparado con alternativas:
- Más moderna que CameraX (solo Android)
- Más integrada que UIImagePickerController (solo iOS)
- Mejor manejo de permisos que la mayoría
- Ventaja multiplataforma sobre soluciones específicas
Agrega la dependencia en tu build.gradle.kts:
dependencies {
implementation("io.github.ismoy:imagepickerkmp:1.0.22")
}Android (AndroidManifest.xml):
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />iOS (Info.plist):
<key>NSCameraUsageDescription</key>
<string>Esta app necesita acceso a la cámara para capturar fotos</string>Para uso básico, no se requiere configuración adicional. La librería maneja la mayoría de la configuración automáticamente.
Para funciones avanzadas, podrías necesitar:
-
Configurar temas personalizados
-
Añadir diálogos de permisos personalizados
-
Configurar preferencias de captura de fotos
-
Agrega a tu proyecto iOS:
# Podfile target 'YourApp' do use_frameworks! pod 'ImagePickerKMP', :path => '../ruta/a/tu/libreria' end
-
Ejecuta pod install:
pod install
-
Importa en tu código iOS:
import ImagePickerKMP
@Composable
fun MyImagePicker() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Take Photo")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
println("Photo captured: ${photo.uri}")
}
is ImagePickerResult.Error -> {
println("Error: ${result.exception.message}")
}
is ImagePickerResult.Dismissed -> {
println("User dismissed")
}
else -> { /* Idle */ }
}
}La librería maneja los permisos automáticamente, pero puedes personalizar el comportamiento:
@Composable
fun CustomPermissionHandler() {
RequestCameraPermission(
titleDialogConfig = "Permiso de cámara requerido",
descriptionDialogConfig = "Por favor habilita el acceso a la cámara",
btnDialogConfig = "Abrir ajustes",
onPermissionPermanentlyDenied = {
// Manejar denegación permanente
},
onResult = { granted ->
// Manejar resultado de permiso
}
)
}@Composable
fun CustomImagePicker() {
val picker = rememberImagePicker(
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.HIGH_QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customPermissionHandler = { config ->
// Manejo personalizado de permisos
},
customConfirmationView = { result, onConfirm, onRetry ->
// Vista de confirmación personalizada
}
)
)
)
Button(onClick = { picker.launchCamera() }) {
Text("Tomar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// Manejar foto capturada
}
is ImagePickerResult.Error -> {
// Manejar errores
}
is ImagePickerResult.Dismissed -> {
// Usuario canceló
}
else -> { /* Idle */ }
}
}@Composable
fun HighQualityImagePicker() {
val picker = rememberImagePicker(
preference = CapturePhotoPreference.HIGH_QUALITY
)
Button(onClick = { picker.launchCamera() }) {
Text("Capturar en alta calidad")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// Manejar foto de alta calidad
}
is ImagePickerResult.Error -> {
println("Error: ${result.exception.message}")
}
else -> {}
}
}La librería proporciona el estado ImagePickerResult.Dismissed que se activa cuando el usuario cancela o cierra el selector sin seleccionar nada. Esto es esencial para resetear el estado de tu UI.
@Composable
fun MiSelectorImagen() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Tomar Foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val foto = result.photos.first()
println("Foto capturada: ${foto.uri}")
}
is ImagePickerResult.Error -> {
println("Error: ${result.exception.message}")
}
is ImagePickerResult.Dismissed -> {
println("Usuario canceló o cerró el selector")
}
else -> { /* Idle */ }
}
}@Composable
fun MiSelectorGaleria() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchGallery(allowMultiple = true) }) {
Text("Seleccionar de la Galería")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val fotos = result.photos
println("Seleccionadas ${fotos.size} imágenes")
}
is ImagePickerResult.Error -> {
println("Error: ${result.exception.message}")
}
is ImagePickerResult.Dismissed -> {
println("Usuario canceló la selección de galería")
}
else -> { /* Idle */ }
}
}Cuándo se activa ImagePickerResult.Dismissed:
- Android: Usuario cancela el diálogo de selección o la interfaz de cámara
- iOS: Usuario toca "Cancelar" en el diálogo o la interfaz de cámara
- iOS: Usuario cancela la solicitud de permisos de cámara
- iOS: Usuario cancela la interfaz de cámara (toca "Cancel" o "X")
Similitudes:
- Misma interfaz de API
- Mismo manejo de permisos
- Mismo manejo de errores
- Mismas opciones de personalización
Diferencias:
- Android usa CameraX, iOS usa AVFoundation
- El flujo de permisos es ligeramente diferente (iOS muestra ajustes tras la primera denegación)
- Algunas optimizaciones específicas de plataforma
@Composable
fun PlatformSpecificImagePicker() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// Manejo agnóstico de plataforma
}
is ImagePickerResult.Error -> {
when (result.exception) {
is CameraPermissionException -> {
// Manejar errores de permisos
}
is PhotoCaptureException -> {
// Manejar errores de captura
}
else -> {
// Manejar otros errores
}
}
}
is ImagePickerResult.Dismissed -> {
// Usuario canceló
}
else -> {}
}
}Las características específicas de iOS son gestionadas internamente por la librería. No necesitas escribir código específico para la mayoría de los casos.
Para funciones avanzadas de iOS:
@Composable
fun IOSImagePicker() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// Manejo específico de iOS
}
is ImagePickerResult.Error -> {
// Manejo de errores específico de iOS
}
else -> {}
}
}- Permisos: Asegúrate de que el permiso de cámara esté concedido
- Hardware: Verifica que el dispositivo tenga cámara
- Ciclo de vida: Verifica el estado del componente
- Dependencias: Revisa que todas las dependencias estén agregadas
- Revisa el manifest: Asegúrate de declarar el permiso de cámara
- Revisa Info.plist: Asegúrate de tener NSCameraUsageDescription (iOS)
- Revisa la implementación: Usa RequestCameraPermission
- Revisa la plataforma: Verifica la configuración específica
- Hardware: El dispositivo puede no tener cámara
- Permisos: El permiso puede estar denegado
- Cámara en uso: Otra app puede estar usando la cámara
- Simulador: La cámara no está disponible en simulador (usa un dispositivo)
- Memoria: Usa compresión de imagen para fotos grandes
- Ciclo de vida: Maneja correctamente el ciclo de vida
- Manejo de excepciones: Añade manejo de errores adecuado
@Composable
fun RobustImagePicker() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
try {
// Procesar foto de forma segura
processPhoto(photo)
} catch (e: Exception) {
// Manejar errores de procesamiento
showError("Error al procesar la foto: ${e.message}")
}
}
is ImagePickerResult.Error -> {
// Manejar errores de captura
showError("Error de cámara: ${result.exception.message}")
}
else -> {}
}
}// Depurar estado de permisos
fun debugPermissions(context: Context) {
val hasPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
println("Permiso de cámara concedido: $hasPermission")
}La compilación de iOS falla con error de enlazador: _OBJC_CLASS_$_CLLocation o CoreLocation.framework no encontrado
Síntomas:
Could not find or use auto-linked framework '_LocationEssentials': framework '_LocationEssentials' not found
ld: Undefined symbols:
_OBJC_CLASS_$_CLLocation, referenced from:
in ComposeApp[...](libImagePickerKMP:library-cache.a.o)
linker command failed with exit code 1Android y JVM Desktop funcionan correctamente, pero la compilación de iOS falla durante la fase de enlazado.
Causa:
ImagePickerKMP usa CoreLocation internamente (p. ej. para metadatos de ubicación al capturar imágenes). En algunas configuraciones de Xcode / KMP el framework no se enlaza automáticamente, por lo que el linker no puede resolver los símbolos de CLLocation.
Solución:
Añade CoreLocation.framework manualmente en las Build Phases de tu proyecto Xcode:
- Abre tu proyecto iOS (
.xcworkspaceo.xcodeproj) en Xcode. - Selecciona el target de tu app en el navegador de proyecto.
- Ve a Build Phases → Link Binary With Libraries.
- Haz clic en + y busca CoreLocation.
- Selecciona
CoreLocation.frameworky haz clic en Add. - Limpia el build folder (Product → Clean Build Folder, o
⇧⌘K) y vuelve a compilar.
✅ No se requieren cambios en el código — es únicamente un paso de configuración a nivel de proyecto.
Entorno reportado: Xcode 15.2 · iOS 15 · Kotlin 2.2.x · ImagePickerKMP 1.0.35
- Usa URIs en vez de Bitmaps:
@Composable
fun MemoryEfficientImagePicker() {
val picker = rememberImagePicker()
var imageUri by remember { mutableStateOf<Uri?>(null) }
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
// Guarda URI en vez de Bitmap
imageUri = result.photos.first().uri
}
else -> {}
}
// Carga la imagen solo cuando sea necesario
imageUri?.let { uri ->
AsyncImage(
model = uri,
contentDescription = "Captured photo"
)
}
}- Usa compresión de imagen:
@Composable
fun CompressedImagePicker() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
// Comprime la imagen antes de procesar
val compressedImage = compressImage(result.photos.first().uri, 80)
}
else -> {}
}
}- Usa preferencia FAST:
val picker = rememberImagePicker(
preference = CapturePhotoPreference.FAST
)
Button(onClick = { picker.launchCamera() }) {
Text("Captura rápida")
}- Preinicializa la cámara:
// Preinicializa la cámara en background
LaunchedEffect(Unit) {
initializeCamera()
}@Composable
fun LargePhotoHandler() {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
lifecycleScope.launch(Dispatchers.IO) {
// Procesa la foto grande en background
val processedImage = processLargeImage(result.photos.first().uri)
withContext(Dispatchers.Main) {
// Actualiza la UI con la imagen procesada
}
}
}
else -> {}
}
}@Composable
fun CustomPermissionDialog(
title: String,
description: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = { Text(description) },
confirmButton = {
Button(onClick = onConfirm) {
Text("Grant Permission")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
}
)
}@Composable
fun CustomConfirmationView(
result: PhotoResult,
onConfirm: () -> Unit,
onRetry: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize()
) {
// Vista previa de la foto
AsyncImage(
model = result.uri,
contentDescription = "Captured photo",
modifier = Modifier.weight(1f)
)
// Botones de acción
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = onRetry) {
Text("Retry")
}
Button(onClick = onConfirm) {
Text("Use Photo")
}
}
}
}@Composable
fun ThemedImagePicker() {
val customTheme = remember {
MaterialTheme.colors.copy(
primary = Color(0xFF1976D2),
secondary = Color(0xFF42A5F5)
)
}
MaterialTheme(colors = customTheme) {
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// Manejar captura de foto
}
is ImagePickerResult.Error -> {
// Manejar errores
}
else -> {}
}
}
}Problema: Las fotos capturadas con la cámara frontal aparecen con orientación incorrecta (espejadas o rotadas).
Causa: Las cámaras frontales de Android tienen una orientación diferente a las traseras. La imagen se captura con una orientación que no es natural para el usuario.
Solución: La librería ahora incluye corrección automática de orientación para fotos de cámara frontal. El sistema:
- Detecta automáticamente si la foto fue tomada con cámara frontal
- Aplica corrección de espejo solo cuando es necesario
- Mantiene la calidad de la imagen original
- Es eficiente - solo procesa cuando realmente necesita corrección
Ejemplo de uso:
val picker = rememberImagePicker()
Button(onClick = { picker.launchCamera() }) {
Text("Capturar foto")
}
when (val result = picker.result) {
is ImagePickerResult.Success -> {
val photo = result.photos.first()
// La imagen ya viene corregida automáticamente
// No necesitas hacer nada adicional
}
else -> {}
}Nota: Esta corrección se aplica automáticamente y es transparente para el desarrollador. No necesitas configurar nada adicional.
La corrección incluye:
- Lectura de metadatos EXIF: Se lee la orientación original de la imagen
- Aplicación de rotación: Se corrige la rotación basada en los metadatos
- Corrección de espejo: Solo para cámara frontal, se aplica un espejo horizontal
- Optimización: Solo se procesa si realmente es necesario
No, la corrección está optimizada para:
- Procesamiento solo cuando es necesario: Si no hay corrección requerida, se devuelve la imagen original
- Gestión eficiente de memoria: Los bitmaps se reciclan correctamente
- Procesamiento asíncrono: No bloquea la UI
Actualmente la corrección es automática y no se puede desactivar, ya que mejora significativamente la experiencia del usuario. Si necesitas el comportamiento original, puedes procesar la imagen manualmente después de recibirla.
- Documentación: README.es.md
- Referencia de API: API_REFERENCE.es.md
- Ejemplos: EXAMPLES.es.md
- GitHub Issues: GitHub Issues
- Discusiones: GitHub Discussions
- Email: belizairesmoy72@gmail.com
- Busca issues existentes: Verifica si ya fue reportado
- Crea un nuevo issue: Usa la plantilla de bug report
- Proporciona detalles: Incluye pasos para reproducir, info de entorno, logs
- Da seguimiento: Responde a preguntas de los maintainers
- Busca issues existentes: Verifica si ya fue solicitada
- Crea una solicitud: Usa la plantilla de feature request
- Proporciona detalles: Incluye caso de uso, implementación propuesta
- Discute: Participa en la comunidad
- Haz fork del repositorio
- Crea una rama de feature
- Haz tus cambios
- Añade tests
- Envía un pull request
Consulta CONTRIBUTING.es.md para guías detalladas.
¿Aún tienes preguntas? No dudes en preguntar en nuestras GitHub Discussions o contáctanos directamente.