npx claudepluginhub davidortinau/maui-skills --plugin maui-skillsThis skill uses the workspace's default tool permissions.
Single-select returns `null`, multi-select returns an empty list. Not checking causes `NullReferenceException`.
Guides FilePicker APIs, FileSystem helpers, bundled assets, and app data storage in .NET MAUI apps. Covers platform permissions, file formats, and pitfalls on Android, iOS, macOS, Windows.
Implement, review, or improve photo picking, camera capture, and media handling in iOS Swift apps using PhotoKit, AVFoundation, and PhotosPicker.
Guides photo selection, limited library access handling, and camera roll saving in iOS apps using PHPicker, PhotosPicker, and PhotoKit APIs.
Share bugs, ideas, or general feedback.
Single-select returns null, multi-select returns an empty list. Not checking causes NullReferenceException.
// ❌ Crashes when user cancels
var photo = await MediaPicker.Default.PickPhotoAsync();
using var stream = await photo.OpenReadAsync();
// ✅ Always check for null / empty
var photo = await MediaPicker.Default.PickPhotoAsync();
if (photo is null) return;
using var stream = await photo.OpenReadAsync();
All MediaPicker methods must run on the UI thread or they throw.
// ❌ Will throw on some platforms
await Task.Run(async () => await MediaPicker.Default.PickPhotoAsync());
// ✅ Call directly from a command or event handler on the UI thread
var photo = await MediaPicker.Default.PickPhotoAsync();
Devices without cameras (emulators, some tablets) will throw if you call capture methods.
// ❌ Crashes on devices without a camera
var photo = await MediaPicker.Default.CapturePhotoAsync();
// ✅ Guard with capability check
if (MediaPicker.Default.IsCaptureSupported)
{
var photo = await MediaPicker.Default.CapturePhotoAsync();
}
OpenReadAsync() returns a stream that must be disposed.
// ❌ Leaks the stream
var stream = await photo.OpenReadAsync();
var bytes = ReadAllBytes(stream);
// ✅ Dispose with using
using var stream = await photo.OpenReadAsync();
MediaPickerOptions.SelectionLimit is advisory. Always validate the count yourself:
var options = new MediaPickerOptions { SelectionLimit = 5 };
var results = await MediaPicker.Default.PickPhotosAsync(options);
if (results.Count() > 5)
{
// Warn user or take only the first 5
results = results.Take(5);
}
READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGEREAD_MEDIA_IMAGES / READ_MEDIA_VIDEO (broad storage permissions are ignored)android:maxSdkVersion="32" on the old permissions to avoid Play Store warningsMissing any one of NSCameraUsageDescription, NSMicrophoneUsageDescription,
NSPhotoLibraryUsageDescription, or NSPhotoLibraryAddUsageDescription causes
a runtime crash when that feature is accessed.
<queries> for camera intentsWithout the IMAGE_CAPTURE query in AndroidManifest.xml, CapturePhotoAsync
may fail silently on Android 11+ due to package visibility restrictions.
| Scenario | Method |
|---|---|
| User picks one photo | PickPhotoAsync() |
| User picks multiple (.NET 10+) | PickPhotosAsync() with SelectionLimit |
| App needs camera capture | Check IsCaptureSupported → CapturePhotoAsync() |
| Save picked file permanently | Copy stream to FileSystem.AppDataDirectory |
| Need photo metadata | Set PreserveMetaData = true in MediaPickerOptions |
IsCaptureSupported guard before capture methodsusingmaxSdkVersion<queries> block for camera intentInfo.plist has all four usage description keysSelectionLimit validated in code, not trusted from platform