Jetpack Compose: Display a Photo Picker

Chase
5 min readNov 10, 2023

--

Did you know that Google created a way for Jetpack Compose to access the users photos without requesting permission, and it still ensures privacy for the user? We will go over how this works and how you can use it in your own projects.

A screenshot of the default image picker UI in Android

Before we get lost in all the cool things we are about to learn, please take a couple seconds to follow me and 👏 clap for the article so that we can help more people learn about this useful content.

Initial Setup

Before we get started, you will see the term URI used throughout the example. URI stands for Uniform Resource Identifier. URI is an acronym, not a name, so if you pronounce this in front of your colleges (or into a microphone), make sure you say it as individual letters U.R.I. and not as a name, otherwise you may not be taken seriously.

For the tutorial, I have chosen to build for SDK version 34. There is one dependency that we will need to add for the display portion of the tutorial. Note that this isn’t needed to make to picker work, but is used so that we can test this code in your own projects.

// add this to your deopendencies in the App Build.gradle file
implementation("io.coil-kt:coil-compose:2.5.0")

You can check coils docs to see the latest version available here: https://coil-kt.github.io/coil/compose/

After you have added the coil dependency, don’t forget to sync the gradle files (a bar should appear near the top of Android Studio with a button that says “Sync now” after you have added the dependency).

How is this more private

If we were to request access to the users photos library, that would be giving our app access to all the photos in the users library. Using the method below, our app will only have access to the specific photos that the user has explicitly chosen to put in our app. This is great for us as the developer because it gives our apps only what photos are needed, and it is great for the user because they can rest assured knowing that the app in their pocket isn’t creeping 🧟‍♂️ through all their photos.

Accessing the Users Photos in Jetpack Compose

In our example below, we have added all the components to a single file to make the tutorial easier to work with and so that we can easily see how the built in photo selector works.

There are a few things to call out from the PhotosPicker API below, the first is that we are limiting what the user can select only to images for our example. We are also setting a maximum number of photos that the user can select. If for example we wanted the use this code to allow the user to choose a profile photo (or only a single image at a time) we would change the limit to 1 (or remove that parameter from our call site since it defaults to 1). This is not allowed in the API by default, which is why we are doing a little extra logic to make this component more reusable so that we can use it in many more projects in the future. If you know of a better way to make this component more reusable, feel free to add a comment to the post.

We are also adding a LazyRow to display the images that we got back. This is not needed to make the photo picker work, but is included in this tutorial so that you can test your own code and ensure that it is working correctly in your project.

package com.example.imagepickerexample

import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
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.tooling.preview.Preview
import coil.compose.AsyncImage
import com.example.imagepickerexample.ui.theme.ImagePickerExampleTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImagePickerExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
PhotoSelectorView(maxSelectionCount = 3)
}
}
}
}
}

@Composable
fun PhotoSelectorView(maxSelectionCount: Int = 1) {
var selectedImages by remember {
mutableStateOf<List<Uri?>>(emptyList())
}

val buttonText = if (maxSelectionCount > 1) {
"Select up to $maxSelectionCount photos"
} else {
"Select a photo"
}

val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri -> selectedImages = listOf(uri) }
)

// I will start this off by saying that I am still learning Android development:
// We are tricking the multiple photos picker here which is probably not the best way,
// if you know of a better way to implement this feature drop a comment and let me know
// how to improve this design
val multiplePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickMultipleVisualMedia(maxItems = if (maxSelectionCount > 1) {
maxSelectionCount
} else {
2
}),
onResult = { uris -> selectedImages = uris }
)

fun launchPhotoPicker() {
if (maxSelectionCount > 1) {
multiplePhotoPickerLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
} else {
singlePhotoPickerLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}
}

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
launchPhotoPicker()
}) {
Text(buttonText)
}

ImageLayoutView(selectedImages = selectedImages)
}
}

@Composable
fun ImageLayoutView(selectedImages: List<Uri?>) {
LazyRow {
items(selectedImages) { uri ->
AsyncImage(
model = uri,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Fit
)
}
}
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ImagePickerExampleTheme {
PhotoSelectorView()
}
}

If you want to see how to do the same thing in SwiftUI, check out my other article:

If you got value from this article, please consider following me, clapping for this article, or sharing it to help others more easily find it.

If you have any questions on the topic, or know of another way to accomplish the same task, feel free to respond to the post or share it with a friend and get their opinion on it.

If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@jpmtech

If you want to see apps that have been built with native mobile development, you can check out my apps here: https://jpmtech.io/apps

Thank you for taking the time to check out my work!

--

--