Photo Picker In Jetpack Compose

Introduction

The Photo Picker functionality in Jetpack Compose brings a powerful and user-friendly approach to handling media selection within your Android app. With Compose's declarative and modern UI toolkit, developers can effortlessly integrate a photo picker that allows users to select images from their device's gallery or take new photos using the camera.

In this photo picker guide, we'll explore how to incorporate this feature into your app, enabling users to effortlessly upload images, enhance their posts, or personalize their profiles. Whether you're building a social media platform, a multimedia gallery, or any app that requires media uploads, the Jetpack Compose Photo Picker offers an intuitive solution to streamline this essential functionality.

Let's dive into the world of Jetpack Compose's Photo Picker and empower your users to interact with images like never before.

Setup

Add the dependency in 'app/build.gradle'.

implementation "io.coil-kt:coil-compose:2.3.0"

Consider adding the latest dependency of the coil. You can the latest dependency here https://coil-kt.github.io/coil/compose/

This dependency is used here for showing the async image generated from the image URI. This photo picker is available from Android 13, although it is backward compatible.

Step 1. Create a picker for one or multiple images

Here we initialize a variable called imageUri with the initial value of a drawable resource ID (R.drawable.img). You can choose a local image; otherwise, use mutableStateOf<Uri?>(null). The code uses the rememberLauncherForActivityResult function to create two launchers: photoPicker and multiplePhotoPicker. The photoPicker allows the user to select a single visual media item (image or video), and when a selection is made, the selected media's URI is stored in the imageUri variable. The multiplePhotoPicker allows the user to select multiple visual media items, with a maximum limit of any items (as we wrote 2 in the below code ). The first selected media URI is assigned to imageUri. The code also logs the selected URIs or indicates if no media was selected using Log.d statements for debugging purposes. You will receive a list of URIs in case of multiplePhotoPicker.

var imageUri: Any? by remember { mutableStateOf(R.drawable.img) }

var selectedImageUris by remember {
    mutableStateOf<List<Uri>>(emptyList())
}

val photoPicker = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.PickVisualMedia()
) {
    if (it != null) {
        Log.d("PhotoPicker", "Selected URI: $it")
        imageUri = it
    } else {
        Log.d("PhotoPicker", "No media selected")
    }
}

val multiplePhotoPicker = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.PickMultipleVisualMedia(maxItems = 2)
) {
    if (it != null) {
        Log.d("PhotoPicker", "Selected URI: $it")
        selectedImageUris = it
    } else {
        Log.d("PhotoPicker", "No media selected")
    }
}

Note. Below API Level 33, the max item functionality has no effect, which means you can choose multiple images without the cap of 2 in Android 12 or lower devices.

Step 2. Open Picker and show One image

Here we use a composable function, 'AsyncImage', which represents an image component that loads asynchronously. The 'AsyncImage' has a 'modifier' that sets its size to 250 dp and makes it clickable. When the image is clicked, it launches the 'photoPicker', which allows the user to select a visual media (an image in this case). The selected media's URI will be stored in the 'imageUri' variable. The 'AsyncImage' uses the 'model' parameter to define the image request, specifying the image data to be loaded from the 'imageUri'. It also enables crossfade for smooth transitions when the image is loaded. The 'contentDescription' provides an accessible description for the image, and 'contentScale' specifies how the image should be scaled within the composable.

AsyncImage(
    modifier = Modifier
        .size(250.dp)
        .clickable {
            photoPicker.launch(
                PickVisualMediaRequest(
                    ActivityResultContracts.PickVisualMedia.ImageOnly
                )
            )
        },
    model = ImageRequest.Builder(LocalContext.current)
        .data(imageUri)
        .crossfade(enable = true)
        .build(),
    contentDescription = "Avatar Image",
    contentScale = ContentScale.Crop,
)

You can also opt for another option here, but that is currently out of the scope of this article. We will only use the 'ImageOnly' functionality here.

Step 3. Open Picker and show Multiple images

Here we display a row of images using a LazyRow composable. The LazyRow is used to efficiently render horizontally scrolling lists. It takes a list of 'selectedImageUris', and for each uri in the list, it creates a AsyncImage composable. Each AsyncImage displays an image specified by the URI with a size of 250 dp and uses ImageRequest.Builder to load the image asynchronously. The Button allows the user to pick multiple photos using the multiplePhotoPicker. When the Button is clicked, the photo picker is launched with the option to pick only images (ImageOnly).

LazyRow {
    items(selectedImageUris) { uri ->
        AsyncImage(
            modifier = Modifier
                .size(250.dp),
            model = ImageRequest.Builder(LocalContext.current)
                .data(uri)
                .crossfade(enable = true)
                .build(),
            contentDescription = "Avatar Image",
            contentScale = ContentScale.Crop,
        )
    }
}
Spacer(modifier = Modifier.width(24.dp))
    Button(
        onClick = {
            multiplePhotoPicker.launch(
                PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
            )
        }
    ) {
        Text(text = "Pick multiple photo")
    }

Step 4. Check if PhotoPicker is available or not

We can check the availability of the photo picker using the isPhotoPickerAvailable function. The Button is labeled "Availability", and when tapped, it provides a quick way to check if the photo picker is supported on the current device.

Button(onClick = {
    Toast.makeText(
        context,
        ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable().toString(),
        Toast.LENGTH_LONG
    ).show()
}) {
    Text(text = "Availability")
}

Full Code Snippet

Here is the full code snippet of the 'ImagePickerScreen'.

package com.mcn.imagepicker

import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest

@Composable
fun ImagePickerScreen() {
    val context = LocalContext.current
    var imageUri: Any? by remember { mutableStateOf(R.drawable.img) }
    var selectedImageUris by remember {
        mutableStateOf<List<Uri>>(emptyList())
    }
    val photoPicker = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickVisualMedia()
    ) {
        if (it != null) {
            Log.d("PhotoPicker", "Selected URI: $it")
            imageUri = it
        } else {
            Log.d("PhotoPicker", "No media selected")
        }
    }
    val multiplePhotoPicker = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickMultipleVisualMedia(maxItems = 2)
    ) {
        if (it != null) {
            Log.d("PhotoPicker", "Selected URI: $it")
            selectedImageUris = it
        } else {
            Log.d("PhotoPicker", "No media selected")
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        AsyncImage(
            modifier = Modifier
                .size(250.dp)
                .clickable {
                    photoPicker.launch(
                        PickVisualMediaRequest(
                            ActivityResultContracts.PickVisualMedia.ImageOnly
                        )
                    )
                },
            model = ImageRequest.Builder(LocalContext.current).data(imageUri)
                .crossfade(enable = true).build(),
            contentDescription = "Avatar Image",
            contentScale = ContentScale.Crop,
        )
        Spacer(modifier = Modifier.height(24.dp))
        LazyRow {
            items(selectedImageUris) { uri ->
                AsyncImage(
                    modifier = Modifier.size(250.dp),
                    model = ImageRequest.Builder(LocalContext.current).data(uri)
                        .crossfade(enable = true).build(),
                    contentDescription = "Avatar Image",
                    contentScale = ContentScale.Crop,
                )
            }
        }
        Spacer(modifier = Modifier.height(24.dp))
        Row {
            Button(onClick = {
                Toast.makeText(
                    context,
                    ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable().toString(),
                    Toast.LENGTH_LONG
                ).show()
            }) {
                Text(text = "Availability")
            }

            Spacer(modifier = Modifier.width(24.dp))
            Button(onClick = {
                multiplePhotoPicker.launch(
                    PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
                )
            }) {
                Text(text = "Pick multiple photo")
            }
        }
    }

}

Output

Let's See how these work on different versions of Android. The left one is Android 13 (API level 33), and the right one is Android 11 (API level 30).

Summary

This article explains how we can implement a Photo Picker in Android using the Compose framework. By leveraging the built-in components and navigation capabilities of Jetpack Compose, you can create a seamless user experience that simplifies the process of selecting images while ensuring your app stays responsive and visually engaging. Hope this has been a helpful guide for you; it will be helpful for me if you write your genuine comment about this article. Thank you!


Similar Articles