Exploring the Android Photo Picker

The Photo Picker allow us to reduce friction when working with Media selection on Android, along with creating a simpler + consistent experience for users. In this blog post, we’re going to take a quick look at how we can use it in our own apps – both in Composable UI and within activities/fragments.


Looking to learn Jetpack Compose? Check out my course, Practical Jetpack Compose


Why use the Photo Picker?

Whether you already have media pickers implemented in your app, or are looking at adding support, the Photo Picker gives us some advantages out of the box.

  • Media Permissions are handled for us, regardless of Android Version. For example, Android 14 saw the introduction of partial media access. The Photo Picker handles these permissions for us, so we don’t need to invest in additional effort for implementing and maintaining complex permission flows
  • Picker availability is managed by the API. If the Photo Picker is available then this will be used, otherwise, the API will use one of the PICK_IMAGES or GET_DOCUMENTS system alternatives as a fallback
  • The Photo Picker gives our users a consistent experience across the android ecosystem, while also modernising and simplifying the media selection process. The Photo Picker hides a lot of the noise that comes with the standard system pickers, allowing our users to focus on the desired media selection

These are only some of the main advantages for using the photo picker, and i’m sure there are more that others have experience from their migration of traditional approaches.


Implementation in Activities/Fragments

When it comes to utilising the photo picker inside of activities or fragments, we need to do two things:

  • Register the result request using a contract that will be used to receive the result
  • Create the activity request and use the contract to launch it

We’ll start here by registering our activity/fragment using the registerForActivityResult function. When doing this, we need to provide both the contract and callback.

private val picker = registerForActivityResult(//contract) {
    // callback
 }

For the contract, we will be using PickMultipleVisualMedia, this is a system intent that will launch the Photo Picker – if the Photo Picker is not available, alternative system pickers will be used instead.

ActivityResultContracts.PickMultipleVisualMedia()

When using the PickMultipleVisualMedia contract, we can also provide a value for the maximum number of media that can be selected. If not provided, the default value supported by the picker will be used.

PickMultipleVisualMedia(10)

Alternatively, the PickVisualMedia contract can be used to allow the selection of a single media item.

ActivityResultContracts.PickVisualMedia()

Regardless of the contract being used, we next need to slot this into our registerForActivityResult request. Along with the callback that will handle our request – we won’t be implementing the body of this callback in this example, but the callback will simplify provide us with the list of Uris from the multiple media request. If only supporting a single media item via PickVisualMedia, this will be a Uri instead of a list.

private val picker = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris ->
    // handle uris
 }

With our request defined, we can now trigger a media picker request using the launch function on our picker reference. When calling this function, we need to pass a PickVisualMediaRequest reference containing the supported media type. This takes the form of a VisualMediaType reference that can be either ImageAndVideo, ImageOnly, VideoOnly or SingleMimeType to provide the mimetype that you want the picker to filter by.

picker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo))

When triggering this launch function, the photo picker will be displayed to the user using the specified constraints. Once the media selection has been confirmed, the result will be delivered to the callback provided to the registerForActivityResult function.


Implementation in Compose

When it comes to using the photo picker in compose, the developer experience is consistent to using it within an activity/fragment, things only differ slightly for the adaption within the compose world.

We must start by defining our result receiver using the rememberLaunchForActivityResult function. Similar to what we saw in the previous example, this will create a result receiver using the provided contract and callback. The difference here is that this remember function is specifically for compose and will be remembered across compositions.

 val photoPickerLauncher = rememberLauncherForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(10)) { uris ->
    // Handle URIs
}

With this receiver in place we can now use this within our composables to trigger the Photo Picker within our composable UI. Similar to before, we use the launch function along with the request that we wish to be used. The same availability applies as above, allowing us to restrict the types of media that is displayed within the Photo Picker.

photoPickerLauncher.launch(
    PickVisualMediaRequest(
        ActivityResultContracts.PickVisualMedia.ImageAndVideo
    )
)

In the above examples, we’ve learnt how we can adopt the Photo Picker in both our Compose and Android View UI. These examples aren’t extensive and there’s much more you can do with the Photo Picker, so I’d love to hear how you are/plan on using it in your own apps!