KEMBAR78
[.NET 10] Implement multi-select for MediaPicker by jfversluis · Pull Request #30002 · dotnet/maui · GitHub
Skip to content

Conversation

jfversluis
Copy link
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

This PR adds the ability to pick multiple files at once through MediaPicker. The single pick methods are obsoleted in favor of the multi pick ones. This prevents the need to have a separate options objects or parameter etc. Now the MediaPickerOptions object has a new field SelectionLimit with a default value of 1. So default is still a single pick action. Then 0 means unlimited and any other number is the actual limit.

There are a few notes:

  • iOS, works as expected, just set the limit and the UI will block the user from picking more than configured
  • Android, the default UI blocks the user, but on Android people have the option to use their own custom picker. Those pickers are not guaranteed to honor the maximum selection limit, so be aware.
  • Windows, does not have any selection limit implemented at all. This is not available through the Windows APIs. Make sure to implement your own code to detect this.

Issues Fixed

Fixes #29079

@jfversluis jfversluis added this to the .NET 10.0-preview6 milestone Jun 16, 2025
@Copilot Copilot AI review requested due to automatic review settings June 16, 2025 08:33
@jfversluis jfversluis requested a review from a team as a code owner June 16, 2025 08:33
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements multi-select support for MediaPicker in .NET MAUI by adding new async methods and extending MediaPickerOptions with a SelectionLimit property. Key changes include:

  • New async methods (PickPhotosAsync, PickVideosAsync) returning lists of FileResult objects and obsolete single-selection methods.
  • Platform-specific updates in Android, iOS, and UWP to support multiple file selections.
  • Updates to sample ViewModel and XAML to demonstrate multi-select functionality.

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

File Description
PublicAPI.Unshipped.txt (netstandard, net, net-windows, net-maccatalyst, net-ios, net-android) Updated public API definitions to include multi-select methods and the new SelectionLimit property.
Platform/PickMultipleVisualMediaForResult.android.cs Introduced a new helper class to handle multi-select on Android.
MediaPicker.uwp.cs, MediaPicker.ios.cs, MediaPicker.android.cs Modified platform-specific implementations to support multiple media picks.
Samples (ViewModel and XAML) Adapted sample code to use multi-select methods and display multiple photos.
Comments suppressed due to low confidence (2)

src/Essentials/src/MediaPicker/MediaPicker.ios.cs:176

  • Returning null in the multiple-selection method for iOS when the OS version is less than 14 can lead to potential null reference issues; consider returning an empty list instead for consistency with other implementations.
if (!OperatingSystem.IsIOSVersionAtLeast(14, 0)) { return null; }

src/Essentials/samples/Samples/ViewModel/MediaPickerViewModel.cs:131

  • [nitpick] The sample now reuses the PhotoSource property for both photo and video operations, which could be confusing; consider introducing a separate property for video to clarify intent and usage.
var video = await MediaPicker.CaptureVideoAsync();

@jfversluis jfversluis moved this to Ready To Review in MAUI SDK Ongoing Jun 16, 2025
@phunkeler
Copy link

phunkeler commented Jun 16, 2025

Maybe this is the subject of future work, but how do you feel about simplifying the API even further and having just a single PickAsync method and moving the media type specification to the options. This would allow the selection of both images and videos (provided each platform supports it).

Something like:

Definition
/// <summary>
/// Represents the type of media that can be picked using the MediaPicker API.
/// </summary>
public enum PickableMediaType
{
    /// <summary>
    /// Represents all types of media that can be selected.
    /// </summary>
    All = 0,

    /// <summary>
    /// Represents image media that can be selected.
    /// </summary>
    Image = 1,

    /// <summary>
    /// Represents video media that can be selected.
    /// </summary>
    Video = 2,
}

/// <summary>
/// Pick options for picking media from the device.
/// </summary>
public class MediaPickerOptions
{
    /// <summary>
    /// Gets or sets the type of media that can be picked.
    /// Default value is <see cref="PickableMediaType.All"/>.
    /// </summary>
    public PickableMediaType PickableMediaType { get; set; } = PickableMediaType.All;

    /// <summary>
    /// Gets or sets the title that is displayed when picking media.
    /// </summary>
    /// <remarks>This title is not guaranteed to be shown on all operating systems.</remarks>
    public string? Title { get; set; }

    /// <summary>
    /// Gets or sets the maximum number of items that can be selected. Default value is 1.
    /// </summary>
    /// <remarks>
    /// A value of 0 means no limit.
    /// </remarks>
    public int SelectionLimit { get; set; } = 1;
}

/// <summary>
/// The MediaPicker API lets a user pick or take a photo or video on the device.
/// </summary>
public interface IMediaPicker
{
    /// <summary>
    /// Gets a value indicating whether capturing media is supported on this device.
    /// </summary>
    bool IsCaptureSupported { get; }

    // NEW
    Task<IEnumerable<FileResult>> PickAsync(MediaPickerOptions? options = null);

    /// <summary>
    /// Opens the media browser to select a photo.
    /// </summary>
    /// <param name="options">Pick options to use.</param>
    /// <returns>A <see cref="FileResult"/> object containing details of the picked photo. When the operation was cancelled by the user, this will return <see langword="null"/>.</returns>
    /// <remarks>When using <see cref="MediaPickerOptions.SelectionLimit"/> on this overload, it will <b>not</b> have effect.</remarks>
    [Obsolete($"Use {nameof(PickAsync)} instead.")]
    Task<FileResult?> PickPhotoAsync(MediaPickerOptions? options = null);

    /// <summary>
    /// Opens the camera to take a photo.
    /// </summary>
    /// <param name="options">Pick options to use.</param>
    /// <returns>A <see cref="FileResult"/> object containing details of the captured photo. When the operation was cancelled by the user, this will return <see langword="null"/>.</returns>
    Task<FileResult?> CapturePhotoAsync(MediaPickerOptions? options = null);

    /// <summary>
    /// Opens the media browser to select a video.
    /// </summary>
    /// <param name="options">Pick options to use.</param>
    /// <returns>A <see cref="FileResult"/> object containing details of the picked video. When the operation was cancelled by the user, this will return <see langword="null"/>.</returns>
    /// <remarks>When using <see cref="MediaPickerOptions.SelectionLimit"/> on this overload, it will <b>not</b> have effect.</remarks>
    [Obsolete($"Use {nameof(PickAsync)} instead.")]
    Task<FileResult?> PickVideoAsync(MediaPickerOptions? options = null);

    /// <summary>
    /// Opens the camera to take a video.
    /// </summary>
    /// <param name="options">Pick options to use.</param>
    /// <returns>A <see cref="FileResult"/> object containing details of the captured video. When the operation was cancelled by the user, this will return <see langword="null"/>.</returns>
    Task<FileResult?> CaptureVideoAsync(MediaPickerOptions? options = null);
}
Usage
IEnumerable<FileResult> singleImage = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick an image",
    SelectionLimit = 1,
    PickableMediaType = PickableMediaType.Image
});

IEnumerable<FileResult> images = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick images",
    SelectionLimit = 5,
    PickableMediaType = PickableMediaType.Image
});

IEnumerable<FileResult> singleVideo = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick a video",
    SelectionLimit = 1,
    PickableMediaType = PickableMediaType.Video
});

IEnumerable<FileResult> videos = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick videos",
    SelectionLimit = 5,
    PickableMediaType = PickableMediaType.Video
});

IEnumerable<FileResult> media = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick image or video",
    SelectionLimit = 1,
    PickableMediaType = PickableMediaType.All
});

IEnumerable<FileResult> medias = await MediaPicker.PickAsync(new MediaPickerOptions
{
    Title = "Pick images and/or videos",
    SelectionLimit = 5,
    PickableMediaType = PickableMediaType.All
});

Seems like an easy win, but maybe there's something with FileResult that makes it a bit more complex.

@jfversluis
Copy link
Member Author

Oh I kinda like that idea... Are we sure this is possible for all platforms? Mainly mixing between images and videos?

@jfversluis jfversluis force-pushed the net10-mediapicker-multiselect branch from 6514b74 to 5dda8f1 Compare June 17, 2025 09:33
@PureWeen PureWeen added the p/0 Work that we can't release without label Jun 18, 2025
@github-project-automation github-project-automation bot moved this from Ready To Review to Approved in MAUI SDK Ongoing Jun 20, 2025
@jfversluis jfversluis merged commit 0e0b35b into net10.0 Jun 20, 2025
129 checks passed
@jfversluis jfversluis deleted the net10-mediapicker-multiselect branch June 20, 2025 18:48
@github-project-automation github-project-automation bot moved this from Approved to Done in MAUI SDK Ongoing Jun 20, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Jul 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants