Skip to content

Repository files navigation

Image Picker Android

Linktree Static Badge X LinkedIn Medium Mastodon Bluesky Dev.to blog YouTube Static Badge

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.

🌟 Features

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.

πŸ€” Why Use This Library?

  • 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.

πŸ–ΌοΈ Preview (Demo)

Views (XML) Jetpack Compose

Demo project

Demo project


πŸ› οΈ Versioning

  • 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

!! IMPORTANT NOTE

THE BETA RELEASES MAY CONTAIN MAJOR OR MINOR CHANGES.


🚚 Migration Guide

⚠️ Breaking changes? Check MIGRATION.md before upgrading.


βš™οΈ Basic Configuration (Gradle Dependencies)

Important

Check my article with the implementation
πŸ‘‰ ImagePickerAndroid - My Android Image Picker Library πŸ§‘β€πŸ’» - Medium πŸ‘ˆ

Groovy

implementation 'com.github.NicosNicolaou16:ImagePickerAndroid:2.6.1'
allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

Kotlin DSL

implementation("com.github.NicosNicolaou16:ImagePickerAndroid:2.6.1")
dependencyResolutionManagement {
    //...
    repositories {
        //...
        maven { url = uri("https://jitpack.io") }
    }
}

libs.versions.toml

[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") }
    }
}

πŸ“· Camera Permission (Optional)

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>

πŸš€ Standard Configuration (XML)

Step 1 - Get Instance

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)
    }
    //...
}

Step 2 - Initialize the methods for Image Pickers (choose the preferred method(s))

imagePicker?.initPickSingleImageFromGalleryResultLauncher()

imagePicker?.initPickMultipleImagesFromGalleryResultLauncher()

imagePicker?.initTakePhotoWithCameraResultLauncher()

imagePicker?.initPickSingleVideoFromGalleryResultLauncher()

Step 3 Call from Click Listeners (choose the preferred method(s))

imagePicker?.pickSingleImageFromGallery()

imagePicker?.pickMultipleImagesFromGallery()

imagePicker?.takeSinglePhotoWithCamera()

imagePicker?.pickSingleVideoFromGallery()

Step 4 - Callbacks (Optionals)

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
    }
}

πŸš€ Compose Configuration

Step 1 - Initialize the Callbacks (Optionals)

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
})

Step 2 Call from Click Listeners (choose the preferred method(s))

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()

Example for Compose Implementation

@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
}

⭐ Stargazers

If you enjoy this project, please give it a star! Check out all the stargazers here: Stargazers on GitHub


πŸ™ Support & Contributions

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.

Releases

Packages

Used by

Contributors

Languages