Fix MediaPicker and FilePicker returning null on cancellation#32656
Fix MediaPicker and FilePicker returning null on cancellation#32656jfversluis merged 6 commits intoinflight/currentfrom
Conversation
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Co-authored-by: kubaflo <42434498+kubaflo@users.noreply.github.com>
Co-authored-by: kubaflo <42434498+kubaflo@users.noreply.github.com>
PR Review: Fix MediaPicker.PickPhotosAsync returning null on iOSPR: #32656 SummaryPR correctly fixes API contract violation where Code ReviewAPI Contract ViolationThe documented API contract (MediaPicker.shared.cs lines 32, 60) clearly states:
Yet the iOS implementation had four code paths that violated this by returning Changes AnalysisChange 1: iOS < 14 Early Return (Line 182) // Before
if (!OperatingSystem.IsIOSVersionAtLeast(14, 0))
{
return null; // ❌ API contract violation
}
// After
if (!OperatingSystem.IsIOSVersionAtLeast(14, 0))
{
return []; // ✅ Returns empty list as documented
}Why this works: On iOS < 14, multiple photo selection is unsupported. Returning empty list signals "no selection" rather than causing NullReferenceException. Change 2: Presentation Controller Dismissal (Lines 162, 243) // Before
PickerRef.PresentationController.Delegate = new PhotoPickerPresentationControllerDelegate
{
Handler = () => tcs.TrySetResult(null) // ❌ Null on swipe-to-dismiss
};
// After
PickerRef.PresentationController.Delegate = new PhotoPickerPresentationControllerDelegate
{
Handler = () => tcs.TrySetResult([]) // ✅ Empty list on swipe-to-dismiss
};Why this works: When user dismisses picker by swiping down (iOS gesture) or tapping outside (iPad), the presentation controller delegate fires. Previously returned Note: This code path appears twice in the file - once in Change 3: Helper Method Null/Empty Results (Lines 268-273) // Before
var fileResults = results?
.Select(file => (FileResult)new PHPickerFileResult(file.ItemProvider))
.ToList() ?? [];
// After
// Handle empty or null results - return empty list instead of null
if (results == null || results.Length == 0)
return [];
var fileResults = results
.Select(file => (FileResult)new PHPickerFileResult(file.ItemProvider))
.ToList();Why this is better:
Change 4: Picker Delegate Completion (Line 505) // Before
public override void DidFinishPicking(PHPickerViewController picker, PHPickerResult[] results)
{
picker.DismissViewController(true, null);
CompletedHandler?.Invoke(results?.Length > 0 ? results : null); // ❌ Null on empty
}
// After
public override void DidFinishPicking(PHPickerViewController picker, PHPickerResult[] results)
{
picker.DismissViewController(true, null);
CompletedHandler?.Invoke(results?.Length > 0 ? results : []); // ✅ Empty list
}Why this works: When user taps "Cancel" button or selects nothing, Code Quality✅ Minimal Changes: Only modified null returns to empty list returns Platform ComparisonAndroid Implementation (MediaPicker.android.cs): if (androidUris?.IsEmpty ?? true)
{
return []; // ✅ Already correct
}iOS Implementation Before PR: return null; // ❌ API contract violationiOS Implementation After PR: return []; // ✅ Now matches AndroidEdge Cases AnalysisEdge Case 1: User Swipe-to-Dismiss (iOS)Before PR: Returns Edge Case 2: User Taps Cancel ButtonBefore PR: Returns Edge Case 3: User Taps Outside Picker (iPad)Before PR: Returns Edge Case 4: iOS < 14 DeviceBefore PR: Returns Edge Case 5: Empty Selection (0 photos selected)Before PR: Returns Edge Case 6: Rapid Cancel/ReopenScenario: User opens picker, cancels, opens again, cancels Edge Case 7: SelectionLimit = 1 vs MultipleScenario: User sets Impact on User CodeBefore PR (Unsafe)var photos = await MediaPicker.Default.PickPhotosAsync(options);
// ❌ CRASHES if user cancels - null reference exception
var count = photos.Count;After PR (Safe)var photos = await MediaPicker.Default.PickPhotosAsync(options);
// ✅ SAFE - empty list on cancel
var count = photos.Count; // 0 if cancelledMigration PathUser code that already has null checks will continue to work: var photos = await MediaPicker.Default.PickPhotosAsync(options);
if (photos == null || photos.Count == 0) // Still works, but null check now redundant
return;Testing NotesManual Testing Required: iOS simulator or device needed to test actual picker cancellation scenarios. Test Scenarios:
Test Code (included in Sandbox app): var result = await MediaPicker.Default.PickPhotosAsync(options);
if (result == null)
{
// BUG DETECTED: Should never be null
ResultLabel.Text = "❌ BUG: Result is NULL";
}
else if (result.Count == 0)
{
// CORRECT: Empty list on cancel
ResultLabel.Text = "✅ CORRECT: Empty list";
}
else
{
// SUCCESS: Photos selected
ResultLabel.Text = $"✅ SUCCESS: {result.Count} photo(s)";
}Potential Concerns
|
|
@copilot |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
@copilot make sure to also apply this to Android and Windows |
Android and Windows already return empty lists correctly in all cancellation scenarios: Android (
Windows (
The fix was only needed for iOS, which now matches the behavior of Android and Windows in commit 42b3196. |
There was a problem hiding this comment.
Pull Request Overview
This PR fixes a bug in the iOS implementation of MediaPicker.PickPhotosAsync and PickVideosAsync where null was returned instead of an empty list when users cancelled the operation. This violates the documented API contract stating that cancellation should return an empty list. The fix ensures consistency with Android and Windows implementations.
Key Changes:
- Replace four
nullreturn paths with empty list[]returns - Add explicit null/empty check in
PickerResultsToMediaFileshelper method with early return - Update presentation controller dismissal handler and picker delegate to return empty lists
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Addressed review comments in commit 6d9cab7:
The public API documentation is correct - both Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot specifically https://github.com/dotnet/maui/blob/main/src/Essentials/src/FilePicker/FilePicker.shared.cs#L36 doesn't seem to be correct. It says "/// An IEnumerable of file picking result objects, or if picking was cancelled by the user." Which is not (longer) the case. It will now return an empty collection, right? Update the comment and also the same on line https://github.com/dotnet/maui/blob/main/src/Essentials/src/FilePicker/FilePicker.shared.cs#L57 and https://github.com/dotnet/maui/blob/main/src/Essentials/src/FilePicker/FilePicker.shared.cs#L72 |
…f null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Fixed in commit e26bb48:
|
|
@copilot please undo the change in FileSystem.ios.cs |
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Reverted the change to |
|
/azp run MAUI-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run MAUI-DeviceTests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
* Initial plan * Fix MediaPicker.PickPhotosAsync returning null on iOS Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Complete fix for MediaPicker null return issue Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Address PR review comments: update comment and remove trailing newline Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Fix FilePicker.PickMultipleAsync to return empty collection instead of null on cancellation Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> * Revert FileSystem.ios.cs change - keep original null return behavior Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
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
The iOS implementation of
MediaPicker.PickPhotosAsyncandPickVideosAsyncviolates the documented API contract by returningnullinstead of an empty list when the user cancels. Similarly,FilePicker.PickMultipleAsyncreturnsnullon cancellation on Android and Windows platforms. This causesNullReferenceExceptionin user code that expects non-null collections per the documentation:MediaPicker Changes:
Fixed iOS implementation to return
[]instead ofnullin four code paths:FilePicker Changes:
Fixed Android and Windows platforms to return empty collections instead of
null:FilePicker.android.cs- returns[]onOperationCanceledExceptionFilePicker.windows.cs- returns[]when no files selectedFilePicker.shared.csXML documentation to reflect empty collection return behaviorImpact:
MediaPicker.PickPhotosAsync/PickVideosAsyncnow correctly return empty lists on cancellation (iOS)FilePicker.PickMultipleAsyncnow correctly returns empty collections on cancellation (Android, Windows).Count,.Any(), LINQ operations, etc.Example impact on user code:
Issues Fixed
Fixes #32649
Fixes #32519
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.