A modern and easy-to-use Android library for picking images and videos from the gallery or capturing
them with the camera. It offers a unified API for traditional Views (Activities/Fragments with XML)
and Jetpack Compose.
Note: The example project does not include examples for all methods.
This library is designed to simplify media selection in your Android app with a robust set of features:
- πΌοΈ Single Image Picker: Select a single image from the gallery.
- π¨ Multiple Image Picker: Choose multiple images (up to 9).
- πΈ Camera Capture: Capture a new photo, with streamlined permission handling that can direct users to app settings or can use the onPermanentCameraPermissionDenied() callback to implement your own custom logic).
- πΉ Video Picker: Select a single video from the gallery.
- π Base64 Conversion: Automatically convert selected images to a Base64 string.
- βοΈ Image Scaling: Easily resize images to your desired dimensions.
- π Jetpack Compose Support: First-class support for Jetpack Compose, with dedicated composables.
- Unified API: Supports both traditional Views (Activities/Fragments) and modern Jetpack Compose UIs.
- Time-Saving: Provides a simple, out-of-the-box solution to a common Android task, saving you significant development time.
- Boilerplate Reduction: Handles
ActivityResultLauncher, permissions, and file processing, letting you focus on your app's logic. - Advanced Functionality: Includes powerful features like Base64 encoding and image scaling without needing extra dependencies.
| Views (XML) | Jetpack Compose |
|---|---|
![]() |
![]() |
- JDK Version:
17 - Target SDK:
37 - Minimum SDK:
24 - Kotlin Version:
2.4.20 - Gradle Version:
9.4.1 - Build Tool Version:
37.0.0
THE BETA RELEASES MAY CONTAIN MAJOR OR MINOR CHANGES.
Important
Check my article with the implementation
π ImagePickerAndroid - My Android Image Picker Library π§βπ» - Medium π
implementation 'com.github.NicosNicolaou16:ImagePickerAndroid:2.6.1'allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}implementation("com.github.NicosNicolaou16:ImagePickerAndroid:2.6.1")dependencyResolutionManagement {
//...
repositories {
//...
maven { url = uri("https://jitpack.io") }
}
}[versions]
# other versions here...
imagePickerAndroid = "2.6.1"
[libraries]
# other libraries here...
image-picker-android = { group = "com.github.NicosNicolaou16", name = "ImagePickerAndroid", version.ref = "imagePickerAndroid" }implementation(libs.image.picker.android)dependencyResolutionManagement {
//...
repositories {
//...
maven { url = uri("https://jitpack.io") }
}
}The library declares the CAMERA permission in its manifest, so it is added to your app
automatically and the user is asked for it the first time the camera is opened.
If you don't want the CAMERA permission in your app (for example, if you only use the gallery
pickers, or you don't want the permission dialog), remove it in your app's AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Optional: remove the CAMERA permission added by ImagePickerAndroid.
The camera still works; the library opens it without asking for permission. -->
<uses-permission
android:name="android.permission.CAMERA"
tools:node="remove" />
</manifest>class MainActivity : AppCompatActivity(), ImagePickerInterface {
//...
private var imagePicker: ImagePicker? = null
//...
fun initImagePicker() {
//Builder
//Note: fragmentActivity or fragment are mandatory one of them
imagePicker = ImagePicker(
fragmentActivity = this, //activity instance - private
fragment = this, // fragment instance - private
coroutineScope = lifecycleScope, // mandatory - coroutine scope from activity or fragment - private
scaleBitmapModelForSingleImage = ScaleBitmapModel(
height = 100,
width = 100
), // optional, change the scale for image, by default is null
scaleBitmapModelForMultipleImages = ScaleBitmapModel(
height = 100,
width = 100
), // optional, change the scale for image, by default is null
scaleBitmapModelForCameraImage = ScaleBitmapModel(
height = 100,
width = 100
), // optional, change the scale for image, by default is null
enabledBase64ValueForSingleImage = true, // optional, by default is false - private
enabledBase64ValueForMultipleImages = true, // optional, by default is false - private
enabledBase64ValueForCameraImage = true, // optional, by default is false - private
imagePickerInterface = this, // call back interface
shouldRedirectedToSettingsIfPermissionDenied = false // optional, by default is true - private, if it set false, need to call the callback onPermanentCameraPermissionDenied()
)
//...other image picker initialization method(s)
}
//...
}imagePicker?.initPickSingleImageFromGalleryResultLauncher()
imagePicker?.initPickMultipleImagesFromGalleryResultLauncher()
imagePicker?.initTakePhotoWithCameraResultLauncher()
imagePicker?.initPickSingleVideoFromGalleryResultLauncher()imagePicker?.pickSingleImageFromGallery()
imagePicker?.pickMultipleImagesFromGallery()
imagePicker?.takeSinglePhotoWithCamera()
imagePicker?.pickSingleVideoFromGallery()class MainActivity : AppCompatActivity(), ImagePickerInterface {
//...
override fun onGallerySingleImage(bitmap: Bitmap?, uri: Uri?) {
super.onGalleryImage(bitmap, uri)
//...your code here
}
override fun onCameraImage(bitmap: Bitmap?) {
super.onCameraImage(bitmap)
//...your code here
}
override fun onMultipleGalleryImages(
bitmapList: MutableList<Bitmap>?,
uriList: MutableList<Uri>?
) {
super.onMultipleGalleryImages(bitmapList, uriList)
//...your code here
}
override fun onGallerySingleImageWithBase64Value(
bitmap: Bitmap?,
uri: Uri?,
base64AsString: String?
) {
super.onGalleryImage(bitmap, uri, base64AsString)
//...your code here
}
override fun onCameraImageWithBase64Value(bitmap: Bitmap?, base64AsString: String?) {
super.onCameraImage(bitmap, base64AsString)
//...your code here
}
override fun onMultipleGalleryImagesWithBase64Value(
bitmapList: MutableList<Bitmap>?,
uriList: MutableList<Uri>?,
base64AsStringList: MutableList<String>?
) {
super.onMultipleGalleryImages(bitmapList, uriList, base64AsStringList)
//...your code here
}
override fun onGallerySingleVideo(uri: Uri?) {
super.onGallerySingleVideo(uri)
//...your code here
}
// Need to call and set the shouldRedirectedToSettingsIfPermissionDenied = false from builder to use this callback
override fun onPermanentCameraPermissionDenied() {
super.onPermanentCameraPermissionDenied()
//...your code here
}
override fun onImagePickerNotAvailable() {
super.onImagePickerNotAvailable()
//...your code here
}
}PickSingleImage(
scaleBitmapModel = ScaleBitmapModel(
height = 100,
width = 100
), listener = { bitmap, uri ->
//...your code here
})
PickSingleImageWithBase64Value(
scaleBitmapModel = null,
listener = { bitmap, uri, base64 ->
//...your code here
})
PickMultipleImages(
scaleBitmapModel = null,
listener = { bitmapList, uriList ->
//...your code here
})
PickMultipleImagesWithBase64Values(
scaleBitmapModel = null,
listener = { bitmapList, uriList, base64List ->
//...your code here
})
// The two options (enum) are `TakeImageType.TAKE_IMAGE` or `TakeImageType.TAKE_IMAGE_PREVIEW`,
// by default is `TakeImageType.TAKE_IMAGE`
TakeSingleCameraImage(
scaleBitmapModel = null,
takeImageType = TakeImageType.TAKE_IMAGE,
listener = { bitmap, uri ->
//...your code here
})
// The two options (enum) are `TakeImageType.TAKE_IMAGE` or `TakeImageType.TAKE_IMAGE_PREVIEW`,
// by default is `TakeImageType.TAKE_IMAGE`
TakeSingleCameraImageWithBase64Value(
scaleBitmapModel = null,
takeImageType = TakeImageType.TAKE_IMAGE,
listener = { bitmap, uri, base64 ->
//...your code here
})
PickSingleVideo(listener = { uri ->
//...your code here
})pickSingleImage(onImagePickerNotAvailable = {
// show custom dialog - showDialog.value = true
})
pickSingleImageWithBase64Value(onImagePickerNotAvailable = {
// show custom dialog - showDialog.value = true
})
pickMultipleImages(onImagePickerNotAvailable = {
// show custom dialog - showDialog.value = true
})
pickMultipleImagesWithBase64Values(onImagePickerNotAvailable = {
// show custom dialog - showDialog.value = true
})
/**
* onPermanentCameraPermissionDeniedCallBack is optional
* */
takeSingleCameraImage(context = context, onPermanentCameraPermissionDeniedCallBack {
// show custom dialog - showDialog.value = true
})
takeSingleCameraImageWithBase64Value(context = context, onPermanentCameraPermissionDeniedCallBack {
// show custom dialog - showDialog.value = true
})
pickSingleVideo()@Composable
fun ImagePickerDemo() {
val context = LocalContext.current
// Use Nullable states instead of allocating empty Bitmaps/Uris
var singleImageBitmap by remember { mutableStateOf<Bitmap?>(null) }
var multiImageBitmaps by remember { mutableStateOf<List<Bitmap>>(emptyList()) }
var videoUri by remember { mutableStateOf<Uri?>(null) }
// ==========================================
// 1. Initialize Library Pickers
// ==========================================
PickSingleImage(
scaleBitmapModel = null,
listener = { bitmap, _ -> singleImageBitmap = bitmap }
)
PickMultipleImagesWithBase64Values(
scaleBitmapModel = ScaleBitmapModel(height = 100, width = 100),
maxNumberOfImages = 3,
listener = { bitmapList, _, base64List ->
if (bitmapList != null) {
multiImageBitmaps = bitmapList
base64List?.forEach { base64 ->
Log.d("ImagePicker", "Base64 value retrieved: ${base64.take(20)}...")
}
}
}
)
TakeSingleCameraImage(
scaleBitmapModel = null,
takeImageType = TakeImageType.TAKE_IMAGE_PREVIEW,
listener = { bitmap, _ ->
singleImageBitmap = bitmap
} // Reusing the single image state for preview
)
PickSingleVideo(
listener = { uri -> videoUri = uri }
)
//other code
}If you enjoy this project, please give it a star! Check out all the stargazers here: Stargazers on GitHub
This library is actively maintained. Feedback, bug reports, and feature requests are welcome! Please feel free to open an issue or submit a pull request.

