
Cross-platform image picker and camera library enables seamless camera access, gallery selection, custom UI, and smart permission handling, ensuring a smooth, customizable user experience.
A complete media library for Android Native, Jetpack Compose, Kotlin Multiplatform and iOS. Capture, pick, scan and play media with one modern ecosystem.
| Android Native | Jetpack Compose | Kotlin Multiplatform | Compose Multiplatform | iOS |
|---|---|---|---|---|
| Supported | Supported | Supported | Supported | Supported |
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
minSdk24 · iOS 16.0+
| Android | iOS |
|---|---|
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
}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>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.
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
}
}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
}
}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
}
}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
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-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
| 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 | ✅ | ✅ | ✅ | ✅ | ✅ |
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| 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 |
Contributions are welcome. See CONTRIBUTING.md for setup instructions, code style, and the pull request process.
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 |
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:noneflowchart 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:noneAll modules expose imagepicker-core as an api dependency, so PermissionManager, MediaLogger, I18nKonfig, and PlatformUri are available in your app without an extra dependency declaration.
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|
ismoy 💻 📖 🚧 🎨 🤔 |
medAndro 💻 🐛 |
YaminMahdi 💻 |
jadlr 💻 |
daniil-pastuhov 💻 |
azevio 💻 |
fanqieVip 💻 |
MIT License · Made with ❤️ for the Kotlin Multiplatform community · ⭐ Star this repo
A complete media library for Android Native, Jetpack Compose, Kotlin Multiplatform and iOS. Capture, pick, scan and play media with one modern ecosystem.
| Android Native | Jetpack Compose | Kotlin Multiplatform | Compose Multiplatform | iOS |
|---|---|---|---|---|
| Supported | Supported | Supported | Supported | Supported |
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
minSdk24 · iOS 16.0+
| Android | iOS |
|---|---|
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
}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>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.
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
}
}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
}
}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
}
}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
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-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
| 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 | ✅ | ✅ | ✅ | ✅ | ✅ |
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| 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 |
Contributions are welcome. See CONTRIBUTING.md for setup instructions, code style, and the pull request process.
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 |
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:noneflowchart 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:noneAll modules expose imagepicker-core as an api dependency, so PermissionManager, MediaLogger, I18nKonfig, and PlatformUri are available in your app without an extra dependency declaration.
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|
ismoy 💻 📖 🚧 🎨 🤔 |
medAndro 💻 🐛 |
YaminMahdi 💻 |
jadlr 💻 |
daniil-pastuhov 💻 |
azevio 💻 |
fanqieVip 💻 |
MIT License · Made with ❤️ for the Kotlin Multiplatform community · ⭐ Star this repo