Skip to content

Repository files navigation

ImagePickerKMP

A complete media library for Android Native, Jetpack Compose, Kotlin Multiplatform and iOS. Capture, pick, scan and play media with one modern ecosystem.

Platform Support

Android Native Jetpack Compose Kotlin Multiplatform Compose Multiplatform iOS
Supported Supported Supported Supported Supported

ImagePickerKMP Banner

CI Coverage License Kotlin Stars

Core Photo Video Audio Scanner

Android iOS Desktop Web Compose

Docs   Example   Sponsor


The Ecosystem

ImagePickerKMP is a modular media library. Each module is independent — include only what your app needs.

Module Artifact Description
Photo imagepickerkmp Camera capture and gallery image picking
Video imagepickerkmp-video Video recording and gallery video picking
Audio imagepickerkmp-audio Audio recording with waveform visualization
Audio Player imagepickerkmp-audio-player Voice message / audio file playback
Scanner imagepickerkmp-scanner Barcode and QR code scanning via live camera
Video Player imagepickerkmp-video-player Full-featured video playback with controls

Requirements: Kotlin 2.3.20 · Compose Multiplatform 1.11.1 · Android minSdk 24 · iOS 16.0+


Android iOS
android_imagepickerkmp-photo.mp4
ios_imagepickerkmp-photo.mov

Installation

Add only the modules you need. All modules are published to Maven Central.

// build.gradle.kts (commonMain)
dependencies {
    // Photo — camera capture and gallery image picking
    implementation("io.github.ismoy:imagepickerkmp:1.1.0")

    // Video — video recording and gallery video picking
    implementation("io.github.ismoy:imagepickerkmp-video:1.1.0") // SOON

    // Audio — audio recording
    implementation("io.github.ismoy:imagepickerkmp-audio:1.1.0") // SOON

    // Audio Player — voice message and audio file playback
    implementation("io.github.ismoy:imagepickerkmp-audioplayer:1.1.0") // SOON

    // Scanner — live barcode and QR code scanning
    implementation("io.github.ismoy:imagepickerkmp-scanner:1.1.0") // SOON

    // Video Player — full-featured video playback
    implementation("io.github.ismoy:imagepickerkmp-videoplayer:1.1.0") // SOON
}

iOS — Info.plist

Every module that uses the camera or microphone requires a usage description. Add the ones relevant to your app:

<!-- Camera (Photo, Video, Scanner) -->
<key>NSCameraUsageDescription</key>
<string>Required for camera features.</string>

<!-- Microphone (Video, Audio) -->
<key>NSMicrophoneUsageDescription</key>
<string>Required to record audio.</string>

<!-- Photo Library (Photo, Video) -->
<key>NSPhotoLibraryUsageDescription</key>
<string>Required to select media from your library.</string>

Internationalization (i18n)

ImagePickerKMP features out-of-the-box automatic translation (powered by the i18nKonfig Gradle plugin created by Ismoy Belizaire). The UI components will automatically detect the user's device language and display localized strings (permissions, camera UI, etc.) without any extra setup.

Currently, we support 12 languages including English, Spanish, French, Chinese, Japanese, and more.

Want to add your language? We welcome community contributions! Head over to the Core Module README to learn how to fork the repository, add your language to the translations.yaml file, and submit a Pull Request.


Module Quick Start

Photo — imagepickerkmp

Pick images from the gallery or capture with the camera using a single Compose state holder.

@Composable
fun PhotoScreen() {
    val picker = rememberImagePickerKMP(
        config = ImagePickerKMPConfig(
            galleryConfig = GalleryConfig(allowMultiple = true, selectionLimit = 10),
            cropConfig    = CropConfig(enabled = true)
        )
    )

    Button(onClick = { picker.launchCamera() }) { Text("Camera") }
    Button(onClick = { picker.launchGallery() }) { Text("Gallery") }

    when (val result = picker.result) {
        is ImagePickerResult.Success   -> result.photos.forEach { photo ->
            Image(painter = photo.loadPainter(), contentDescription = null)
        }
        is ImagePickerResult.Loading   -> CircularProgressIndicator()
        is ImagePickerResult.Error     -> Text("Error: ${result.exception.message}")
        is ImagePickerResult.Dismissed -> Unit
        is ImagePickerResult.Idle      -> Unit
    }
}

Full Photo documentation


Video — imagepickerkmp-video

Record video or pick from the gallery. Supports compression, metadata, and multiple formats.

@Composable
fun VideoScreen() {
    val picker = rememberVideoPicker(
        config = VideoPickerConfig(
            audio = AudioConfig.Default,
            output = VideoOutputConfig(
                format = VideoOutputFormat.MP4,
                removeMetadata = false
            ),
            allowedMimeTypes = listOf(VideoMimeType.All)
        )
    )

    Button(onClick = { picker.launchCamera() })  { Text("Record") }
    Button(onClick = { picker.launchGallery() }) { Text("Pick Video") }

    when (val result = picker.result) {
        is VideoPickerState.Success -> Text("Duration: ${result.video.durationMs}ms")
        is VideoPickerState.Error   -> Text("Error: ${result.cause}")
        else -> Unit
    }
}

Full Video documentation


Audio — imagepickerkmp-audio

Two APIs — an inline chat-style mic widget and a modal state-holder.

Inline widget — embeds a hold-to-record mic button directly in your layout:

@Composable
fun ChatInputBar() {
    AudioRecorder(
        config   = AudioRecorderConfig(),
        onResult = { audioResult: AudioResult? ->
            if (audioResult != null) {
                println("Recorded: ${audioResult.uri}, ${audioResult.durationMs}ms")
            }
        }
    )
}

Modal picker — opens recorder or gallery in a dialog, same state-holder pattern as the other modules:

@Composable
fun AudioScreen() {
    val picker = rememberAudioPicker()

    Button(onClick = { picker.launchRecorder() }) { Text("Record") }
    Button(onClick = { picker.launchGallery() })  { Text("Pick Audio") }

    when (val state = picker.result) {
        is AudioPickerState.Success -> Text("Saved: ${state.audio.fileName}")
        else -> Unit
    }
}

Full Audio documentation


Audio Player — imagepickerkmp-audio-player

Low-level playback engine. Powers the ImagePickerAudioPlayer composable in imagepickerkmp-audio and can be used directly to build fully custom player UIs.

@Composable
fun CustomPlayerScreen(audioUri: String) {
    val playerManager = rememberAudioPlayerManager()
    val state by playerManager.playbackState.collectAsState()

    LaunchedEffect(audioUri) { playerManager.prepare(audioUri) }

    LinearProgressIndicator(
        progress = {
            if (state.durationMs > 0) state.currentPositionMs.toFloat() / state.durationMs else 0f
        }
    )

    FloatingActionButton(
        onClick = {
            if (state.isPlaying) playerManager.pause()
            else playerManager.play(audioUri)
        }
    ) {
        Icon(
            imageVector = if (state.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
            contentDescription = null
        )
    }
}

Full Audio Player documentation


Scanner — imagepickerkmp-scanner

Scan barcodes and QR codes from a live camera feed. Supports 19 barcode formats.

@Composable
fun ScannerScreen() {
    val scanner = rememberScannerPicker(
        config = ScannerPickerConfig(
            camera = ScannerCameraConfig(
                behavior = ScannerBehaviorConfig(
                    allowedFormats = listOf(BarcodeFormat.QR_CODE, BarcodeFormat.EAN_13)
                )
            )
        )
    )

    Button(onClick = { scanner.launchScanner() }) { Text("Scan") }

    when (val result = scanner.result) {
        is ScannerPickerState.Success -> Text("Scanned: ${result.result.code}")
        is ScannerPickerState.Error   -> Text("Error: ${result.error}")
        else -> Unit
    }
}

Full Scanner documentation


Video Player — imagepickerkmp-video-player

Full-featured video player with play/pause, seek, volume, fullscreen, and quality selection.

@Composable
fun VideoPlayerScreen(videoUrl: String) {
    ImagePickerVideoPlayer(
        source = VideoSource.Url(videoUrl),
        config = VideoPlayerConfig(
            behavior = VideoBehaviorConfig(autoPlay = true)
        )
    )
}

For programmatic control (play/pause from code), use rememberVideoPlayerState:

val player = rememberVideoPlayerState(source = VideoSource.Url(videoUrl))
ImagePickerVideoPlayer(state = player, config = VideoPlayerConfig())
player.seekTo(30_000L)

Full Video Player documentation


Platform Support Matrix

Feature Android iOS Desktop JS/Web WASM
Photo — Camera
Photo — Gallery
Photo — Crop
Photo — EXIF
Video — Camera
Video — Gallery
Audio — Record
Audio — Player
Scanner
Video Player

PhotoResult — Shared Result Model

All photo operations return a PhotoResult with consistent extension functions across platforms:

val photo: PhotoResult = result.photos.first()

// Compose UI
val painter = photo.loadPainter()       // Painter for Image()
val bitmap  = photo.loadImageBitmap()  // ImageBitmap for Canvas

// Raw data
val bytes  = photo.loadBytes()         // ByteArray
val base64 = photo.loadBase64()        // Base64-encoded string

// File system (kotlinx-io)
val path   = photo.asPath()            // kotlinx.io.files.Path
val exists = photo.exists()            // Boolean
val source = photo.asSource()          // Buffered Source

// Transfer
photo.transferToSink(mySink)           // Copy to RawSink

Documentation

Guide Description
Changelog Release history and migration notes
Contributing How to contribute
Examples Full code examples
Integration Guide Gradle setup, iOS config, ProGuard
FAQ Common questions and troubleshooting
Privacy Guide GDPR, GPS redaction, metadata handling
Security Reporting vulnerabilities

Contributing

Contributions are welcome. See CONTRIBUTING.md for setup instructions, code style, and the pull request process.


Sponsors

ImagePickerKMP is free and open source. Maintaining it across five platforms with every Kotlin and Compose Multiplatform release takes significant effort. If this library saves you time in production, please consider sponsoring.

james-codersHT
james-codersHT

→ Become a sponsor


Architecture

ImagePickerKMP follows a layered architecture. All platform-specific logic lives inside each module's androidMain / iosMain source sets. Your app code only ever touches the commonMain API.

flowchart TD
    %% Layer 1
    App["Your App (commonMain UI)<br/><br/>rememberImagePickerKMP()<br/>rememberVideoPicker()<br/>rememberAudioPicker()<br/>rememberScannerPicker()<br/>ImagePickerVideoPlayer()<br/>ImagePickerAudioPlayer()"]

    %% Layer 2
    M1(["imagepickerkmp (photo)"])
    M2(["imagepickerkmp-video"])
    M3(["imagepickerkmp-video-player"])
    M4(["imagepickerkmp-audio"])
    M5(["imagepickerkmp-audio-player"])
    M6(["imagepickerkmp-scanner"])

    %% Layer 3
    Core{{"imagepicker-core<br/><br/>Permissions, FileSystem, I18n, MediaLogger"}}

    %% Layer 4
    Android["Android<br/>Camera Intents, CameraX, MediaPlayer"]
    iOS["iOS<br/>AVFoundation, PHPickerVC"]
    Desktop["Desktop<br/>JVM / AWT"]
    Web["Web<br/>JS / WASM, WebRTC"]

    App --> M1 & M2 & M3 & M4 & M5 & M6
    M1 & M2 & M3 & M4 & M5 & M6 --> Core
    Core --> Android & iOS & Desktop & Web

    style App fill:transparent,stroke:#777,stroke-width:2px,rx:10,ry:10
    style M1 fill:#8e24aa,color:#fff,stroke:none
    style M2 fill:#8e24aa,color:#fff,stroke:none
    style M3 fill:#8e24aa,color:#fff,stroke:none
    style M4 fill:#8e24aa,color:#fff,stroke:none
    style M5 fill:#8e24aa,color:#fff,stroke:none
    style M6 fill:#8e24aa,color:#fff,stroke:none
    style Core fill:transparent,stroke:#777,stroke-width:2px
    style Android fill:#388e3c,color:#fff,stroke:none
    style iOS fill:#1565c0,color:#fff,stroke:none
    style Desktop fill:#388e3c,color:#fff,stroke:none
    style Web fill:#388e3c,color:#fff,stroke:none
Loading

Module dependency graph

flowchart BT
    Photo["imagepickerkmp"]
    Video["imagepickerkmp-video"]
    Scanner["imagepickerkmp-scanner"]
    VideoPlayer["imagepickerkmp-video-player"]
    Audio["imagepickerkmp-audio"]
    AudioPlayer["imagepickerkmp-audio-player"]
    
    Core(("imagepicker-core<br/>(API)"))
    
    Photo -.-> Core
    Video -.-> Core
    Scanner -.-> Core
    VideoPlayer -.-> Core
    Audio -.-> Core
    AudioPlayer -.-> Core

    style Core fill:transparent,stroke:#777,stroke-width:2px
    style Photo fill:#0288d1,color:#fff,stroke:none
    style Video fill:#0288d1,color:#fff,stroke:none
    style Scanner fill:#0288d1,color:#fff,stroke:none
    style VideoPlayer fill:#0288d1,color:#fff,stroke:none
    style Audio fill:#0288d1,color:#fff,stroke:none
    style AudioPlayer fill:#0288d1,color:#fff,stroke:none
Loading

All modules expose imagepicker-core as an api dependency, so PermissionManager, MediaLogger, I18nKonfig, and PlatformUri are available in your app without an extra dependency declaration.

Data flow — Architecture Lifecycle

sequenceDiagram
    autonumber
    actor User as User
    participant App as @Composable UI
    participant State as StateHolder (commonMain)
    participant Engine as Platform Engine (expect/actual)
    
    User->>App: Interaction (Tap "Camera", "Scan", "Play")
    activate App
    App->>State: launchCamera() / launchScanner() / play()
    activate State
    State->>State: Update State (e.g., Loading, Playing)
    State->>Engine: Request Native Action
    activate Engine
    
    rect rgb(240, 248, 255)
        note right of Engine: Platform Specific Implementations
        alt Photo & Video
            Engine->>Permissions: Granted
            Engine->>Engine: Camera Intents / UIImagePicker → Capture & Compress
        else Scanner
            Engine->>Engine: MLKit / Vision API → Detect & Decode QR/Barcode
        else Audio Record
            Engine->>Engine: MediaRecorder / AVAudioRecorder → Encode
        else Media Players
            Engine->>Engine: MediaPlayer / AVPlayer → Buffer & Play
        end
    end
    
    Engine-->>State: Native Callbacks (URIs, Bytes, Playback Info)
    deactivate Engine
    
    State->>State: Map to commonMain models (PhotoResult, AudioResult, etc.)
    State-->>App: Emit State (picker.result / playbackState)
    deactivate State
    
    App->>App: Recompose UI with media/data
    deactivate App
Loading

Contributors

ismoy
ismoy

💻 📖 🚧 🎨 🤔
medAndro
medAndro

💻 🐛
YaminMahdi
YaminMahdi

💻
jadlr
jadlr

💻
b1jarosz
b1jarosz

💻
daniil-pastuhov
daniil-pastuhov

💻
Magmi183
Magmi183

💻
azevio
azevio

💻
fanqieVip
fanqieVip

💻

MIT License · Made with ❤️ for the Kotlin Multiplatform community · ⭐ Star this repo

About

Camera & Gallery Picker for Android, Jetpack Compose, Kotlin Multiplatform and iOS. Supports CameraX, Compose UI, cropping, compression, EXIF, multiple selection and native camera.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

216 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages