flutter-image-picker-production-engineering
Skill8 starsChanged 48 days agoMITView on GitHub
Design, implement, review, debug, test, optimize, and migrate production Flutter media-acquisition flows using image_picker. Use this skill whenever work involves picking or capturing images/videos, XFile lifetime or persistence, Android lost-data recovery, Photo Picker permissions, iOS PHPicker/HEIC, web Blob URLs, desktop camera delegates, multi-select, upload pipelines, memory-heavy readAsBytes usage, thumbnail decoding, media security validation, or deciding whether image_picker is the right abstraction—even for a seemingly simple “pick an image” request.
In this repository70 other files it gives its agents.
- camerawesome-flutterSkill
- dart-args-production-cliSkill
- dart-flutter-equatable-engineeringSkill
- dart-flutter-ffi-systems-engineeringSkill
- dart-metaSkill
- document-camera-frameSkill
- document-scan-flutterSkill
- figma-to-flutter-expertSkill
- flutter-animations-production-engineeringSkill
- flutter-app-hardening-reverse-engineeringSkill
- flutter-app-icon-engineeringSkill
- flutter-application-security-engineeringSkill
- flutter-app-links-deep-link-engineeringSkill
- flutter-cachingSkill
- flutter-concurrency-memory-performanceSkill
- flutter-confetti-engineeringSkill
- flutter-dart-package-engineeringSkill
- flutter-design-system-from-design-mdSkill
- flutter-dio-cache-interceptor-engineeringSkill
- flutter-dio-engineeringSkill
- flutter-doc-scannerSkill
- flutter-drift-persistence-engineeringSkill
- flutter-expert-tipsSkill
- flutter-firebase-analyticsSkill
- flutter-firebase-core-engineeringSkill
- flutter-firebase-messagingSkill
- flutter-get-it-injectable-dependency-injectionSkill
- flutter-go-router-engineeringSkill
- flutter-hive-persistence-engineeringSkill
- flutter-image-performanceSkill
- flutter-lints-static-analysis-engineeringSkill
- flutter-local-notifications-production-engineeringSkill
- flutter-memory-leak-engineeringSkill
- flutter-mobile-developmentSkill
- flutter-motion-engineeringSkill
- flutter-multi-environmentSkill
- flutter-native-splash-engineeringSkill
- flutter-package-plugin-selectionSkill
- flutter-path-provider-production-storageSkill
- flutter-performance-jank-optimizationSkill
- flutter-permission-handler-engineeringSkill
- flutter-pinputSkill
- flutter-production-build-releaseSkill
- flutter-production-engineeringSkill
- flutter-production-loggingSkill
- flutter-pro-image-editor-engineeringSkill
- flutter-secure-storage-security-engineeringSkill
- flutter-sentry-engineeringSkill
- flutter-share-plus-engineeringSkill
- flutter-skeletonizer-engineeringSkill
- flutter-squadron-concurrency-engineeringSkill
- flutter-supabase-engineeringSkill
- flutter-svg-engineeringSkill
- flutter-ui-performance-engineeringSkill
- flutter-url-launcher-engineeringSkill
- flutter-video-player-production-engineeringSkill
- flutter-webview-production-engineeringSkill
- freezed-production-engineeringSkill
- html-to-flutterSkill
- hydrated-blocSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
59 rules. About 0 are common, 39 are distinctive, and 20 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈39
Distinctive · estimate
This author's own choices. Worth a look.
20
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 9
Treat image_picker as a thin platform-mediated media acquisition boundary.
- Distinctive: line 23
Read only what the task needs:
- Distinctive: line 37
Must media survive app/process restart?
- Very specific: line 50
Do not force image_picker onto a feature with different semantics.
- Very specific: line 83
Do not diagnose platform behavior from the top-level version alone.
- Very specific: line 84
Do not write new PickedFile / getImage / getVideo code.
- Distinctive: line 88
Do not report routine cancellation as an error.
- Distinctive: line 98
When replacing existing media, keep the previous selection if the replacement picker is canceled unless product UX explicitly says …
- Very specific: line 101
Prefer XFile until another API truly requires another representation.
- Very specific: line 108
Use readAsBytes() only when a downstream API genuinely requires the whole buffer and size is safe.
- Distinctive: line 119
If media must survive process restart, app restart, cache eviction, delayed upload, offline draft, or long-lived editing:
- Very specific: line 127
If the flow is pick → upload now → discard, an extra durable copy may be unnecessary.
- Distinctive: line 128
Do not store picker paths in a database and assume durability.
- Distinctive: line 135
Only the application-owned artifacts need your cleanup policy.
- Very specific: line 140
call retrieveLostData() at an application startup/recovery boundary.
- Very specific: line 145
Do not call recovery from widget build().
- Distinctive: line 146
Do not declare Android complete without lifecycle-interruption testing.
- Very specific: line 150
Do not add READ_EXTERNAL_STORAGE or modern broad media permissions solely because the app has an image_picker gallery button.
- Very specific: line 152
Do not add Android camera permission “just in case”.
- Distinctive: line 157
Add relevant usage descriptions:
- Distinctive: line 162
Test HEIC/HEIF on real devices when backend compatibility matters.
- Distinctive: line 165
Ensure the user-selected read-only file entitlement when required.
- Distinctive: line 169
If the desktop capture UX is complex, use a dedicated camera architecture instead of stretching the delegate abstraction.
- Distinctive: line 178
Test actual supported browsers, especially Safari if shipped.
- Distinctive: line 190
Do not rely on:
- Very specific: line 194
Use maxDuration for recorded-video limits where supported, and inspect gallery video duration afterward.
- Distinctive: line 198
Do not rename an extension to “convert” media.
- Distinctive: line 199
If normalization is required:
- Distinctive: line 210
If metadata removal is a product/privacy requirement:
- Distinctive: line 216
Test orientation across camera/gallery, portrait/landscape, resizing, and recompression.
- Distinctive: line 217
Do not log user media bytes, sensitive local paths, filenames, EXIF, coordinates, or Blob URLs without an approved need.
- Very specific: line 238
Do not put large image/video Uint8Lists into frequently rebuilt/equality-compared Bloc/Riverpod/ChangeNotifier state without a demonstrated …
- Distinctive: line 247
Keep large videos stream/file-backed.
- Distinctive: line 257
Do not decode a 48 MP original for a tiny avatar.
- Distinctive: line 258
Do not assume native image-provider decode behavior maps directly to web.
- Very specific: line 261
Use maxWidth/maxHeight when:
- Distinctive: line 265
Do not rely on picker resize as the sole cross-platform enforcement.
- Very specific: line 267
Treat imageQuality as encoder quality, not percentage-of-file-size:
- Distinctive: line 271
Avoid repeated lossy recompression.
- Very specific: line 283
Do not Future.wait unbounded full-byte reads or CPU-heavy transforms.
- Distinctive: line 296
Do not move arbitrary plugin calls into workers just because later processing is heavy.
- Very specific: line 300
Before using BuildContext or widget state after await, verify the owning UI is still mounted when required.
- Very specific: line 301
Do not put BuildContext inside long-lived media services.
- Distinctive: line 305
If concurrent picker sessions would produce ambiguous state:
- Distinctive: line 313
Do not create a global lock framework unless multiple features genuinely need coordinated ownership.
- Distinctive: line 324
Do not create multiple forwarding layers that add no semantics.
- Distinctive: line 350
Never trust extension, MIME, or a client “validated” flag as authority.
- Very specific: line 354
Do not swallow failures with catch (_) {}.
- Distinctive: line 364
Treat cancellation separately from error telemetry.
- Distinctive: line 368
Use a fake gateway to cover:
- Distinctive: line 379
Use real platform integration tests for:
- Distinctive: line 393
Test the platforms and browsers the product actually ships.
- Distinctive: line 398
read changelogs across the upgrade range.
- Very specific: line 400
remove obsolete PickedFile/get* usage.
- Distinctive: line 405
run unit/widget tests;
- Distinctive: line 406
run representative real-platform tests.
- Distinctive: line 409
Do not treat a federated plugin upgrade as a Dart-only change.
- Distinctive: line 412
When media acquisition fails or becomes slow, inspect:
- Distinctive: line 434
no Android lost-data recovery
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Flutter Image Picker Production Engineering
Treat image_picker as a thin platform-mediated media acquisition boundary. It is not durable storage, a camera framework, a media-library manager, or a security validator.
Use this pipeline:
acquire
→ recover lifecycle result
→ validate
→ optionally transform
→ preview
→ persist only when durable ownership is required
→ upload/consume
→ clean application-owned artifactsOptimize the whole pipeline, not just the picker call.
Reference routing
Read only what the task needs:
| Need | Reference |
Package fit, APIs, cancellation, XFile, lifetime, capability matrix | references/01-selection-xfile-platform-basics.md |
| Android, iOS, macOS, Windows/Linux, desktop delegates, web | references/02-platform-integrations.md |
| Resize/quality, formats, memory, streams, persistence, lifecycle, permissions, security | references/03-media-pipeline-performance-security.md |
| State architecture, abstractions, testing, device validation, migration | references/04-architecture-testing-migration.md |
| Audit, anti-patterns, checklists, implementation workflow, completion gates | references/05-audit-workflows-checklists.md |
1. Decide whether image_picker is the right abstraction
Before coding, answer:
- Image, video, or mixed?
- Camera, gallery/system picker, or both?
- Single or multi-select?
- Which platforms ship?
- Must media survive app/process restart?
- What file-size, dimensions, duration, and formats are allowed?
- Is upload immediate or delayed/background?
- What preview size is needed?
- What client/server validation exists?
- Does the feature actually need custom camera preview, controls, frame analysis, albums, or arbitrary files? Choose:
simple image/video selection or simple capture → image_picker
continuous/custom camera/OCR/barcode/frame processing → camera-oriented solution
PDF/ZIP/DOCX/CSV/arbitrary files → file/document picker
albums/assets/library enumeration → gallery/asset-management solutionDo not force image_picker onto a feature with different semantics.
2. Inspect the repository first
Inspect:
pubspec.yaml,pubspec.lock, Flutter/Dart constraints- resolved
image_pickerand platform packages - Android manifest, SDK levels, activity launch mode, permissions
- iOS
Info.plist - macOS entitlements
- supported platforms
- existing camera, file picker, permissions, upload, storage, processing code
- state management, tests, CI Search for:
ImagePicker pickImage pickVideo pickMultiImage pickMultiVideo
pickMedia pickMultipleMedia retrieveLostData PickedFile getImage
getVideo getLostData XFile readAsBytes openRead File(
READ_EXTERNAL_STORAGE READ_MEDIA_IMAGES CAMERA
NSPhotoLibraryUsageDescriptionFlag obsolete APIs, duplicate media layers, broad permissions, missing Android recovery, temp-path persistence, eager bytes, full-resolution previews, swallowed errors, and concurrent picker launches.
3. Version-gate behavior
The supplied baseline is image_picker 1.2.3, Flutter 3.38+, Dart 3.10+, but federated implementations evolve independently.
Inspect resolved:
image_picker_android
image_picker_ios
image_picker_for_web
image_picker_macos
image_picker_windows
image_picker_linux
image_picker_platform_interface
cross_fileDo not diagnose platform behavior from the top-level version alone.
Do not write new PickedFile / getImage / getVideo code. Prefer XFile + current pick* APIs.
4. Treat cancellation as normal
Single selection returns XFile?; null is cancellation/no committed selection.
Multi-selection returns a list; an empty list is cancellation/no committed selection.
Do not report routine cancellation as an error.
Classify outcomes as:
success
cancellation
recoverable platform failure
configuration/programming failure
unexpected system/application failureHandle PlatformException deliberately.
When replacing existing media, keep the previous selection if the replacement picker is canceled unless product UX explicitly says otherwise.
5. Keep XFile as the default media boundary
Prefer XFile until another API truly requires another representation.
Useful operations:
await file.length();
file.openRead();
await file.saveTo(destinationPath);Use readAsBytes() only when a downstream API genuinely requires the whole buffer and size is safe.
Never assume:
XFile == permanent application file
XFile.path == native filesystem path everywhereNative paths may be temporary/cache/provider materializations.
On web, XFile is Blob-backed and path is a Blob URL. Never compile shared web code that does File(file.path). Image.file is unsupported on Flutter web.
6. Make file ownership explicit
If media must survive process restart, app restart, cache eviction, delayed upload, offline draft, or long-lived editing:
temporary/session XFile
→ validate
→ copy/save into application-owned durable storage
→ persist durable app identifier/path
→ schedule durable workIf the flow is pick → upload now → discard, an extra durable copy may be unnecessary.
Do not store picker paths in a database and assume durability.
Separate:
- plugin/platform-owned picker output
- application-created temporary transforms
- application-owned durable media
Only the application-owned artifacts need your cleanup policy.
7. Android lost-data recovery is mandatory production reasoning
Android may destroy the Flutter activity while external picker/camera UI is open. The original Future can then be lost. For production Android support:
- call
retrieveLostData()at an application startup/recovery boundary; - gate it to Android;
- handle recovered files and recovered exceptions;
- pass recovered files through the same validation/persistence/upload flow;
- recover before starting another ambiguous picker session.
Do not call recovery from widget
build(). Do not declare Android complete without lifecycle-interruption testing.
8. Avoid obsolete Android permissions
Normal modern user-mediated media selection does not justify broad legacy storage/media permissions.
Do not add READ_EXTERNAL_STORAGE or modern broad media permissions solely because the app has an image_picker gallery button.
Separate picker needs from unrelated full-library-management features.
Do not add Android camera permission “just in case”; inspect the app's actual camera features and merged manifest.
Also inspect android:launchMode; singleInstance can break picker result delivery.
9. Configure iOS and desktop targets minimally
iOS
Add relevant usage descriptions:
NSPhotoLibraryUsageDescriptionNSCameraUsageDescriptionNSMicrophoneUsageDescriptiononly when video/audio capture needs it PHPicker-mediated selection is not unrestricted PhotoKit library access. Test HEIC/HEIF on real devices when backend compatibility matters.
macOS
Ensure the user-selected read-only file entitlement when required.
macOS/Windows/Linux camera
Camera is not automatically provided by desktop file-picker implementations. Expose camera only when supportsImageSource(ImageSource.camera) is true or an intentional delegate exists.
If the desktop capture UX is complex, use a dedicated camera architecture instead of stretching the delegate abstraction.
10. Treat web as browser-mediated
On web:
- camera capture is a browser hint, not a guarantee;
acceptis UX filtering, not security validation;- cancellation behavior is browser-dependent;
XFile.pathis Blob-based;- native filesystem assumptions are invalid. Test actual supported browsers, especially Safari if shipped.
11. Revalidate product constraints after selection
Picker arguments are not a universal cross-platform contract. Validate important requirements yourself:
- count
- encoded bytes
- media type/format
- image dimensions
- video duration
- backend compatibility
Do not rely on:
limitbeing enforced by every platform;maxDurationfiltering gallery videos;- desktop resize/quality options behaving like mobile.
Use
maxDurationfor recorded-video limits where supported, and inspect gallery video duration afterward.
12. Do not assume JPEG
Selected images may be JPEG, PNG, HEIC/HEIF, GIF, WebP, or another supported format. Do not rename an extension to “convert” media. If normalization is required:
validate/detect
→ explicit transcode
→ verify output
→ uploadmimeType may be null and is not authoritative security proof.
13. Treat metadata/privacy explicitly
requestFullMetadata: false is not a universal EXIF/location stripping guarantee.
If metadata removal is a product/privacy requirement:
select
→ explicitly sanitize/transform
→ verify resulting fileTest orientation across camera/gallery, portrait/landscape, resizing, and recompression. Do not log user media bytes, sensitive local paths, filenames, EXIF, coordinates, or Blob URLs without an approved need.
14. Avoid eager full-buffer reads
Reject this as a default:
final bytes = await file.readAsBytes();especially for high-resolution images, videos, and batches. Prefer:
final length = await file.length();
final stream = file.openRead();when upload/storage APIs accept streams or paths. Avoid:
XFile → bytes → temp file → bytes → uploadMinimize copies.
15. Keep huge buffers out of UI state
Do not put large image/video Uint8Lists into frequently rebuilt/equality-compared Bloc/Riverpod/ChangeNotifier state without a demonstrated requirement.
Prefer state holding:
session XFile
durable media ID/path
remote URL
upload job ID
processing/progress/error metadataKeep large videos stream/file-backed.
16. Decode previews near display size
Encoded size and decoded memory are different. Use the approximation:
decoded bytes ≈ width × height × 4A high-resolution photo can occupy tens or hundreds of MB decoded.
For native thumbnails, use cacheWidth/cacheHeight near required physical display size when appropriate.
Do not decode a 48 MP original for a tiny avatar.
Do not assume native image-provider decode behavior maps directly to web.
17. Use resize/compression intentionally
Use maxWidth/maxHeight when:
- originals are never needed;
- target dimensions are known;
- supported platforms match the requirement. Do not rely on picker resize as the sole cross-platform enforcement.
Treat imageQuality as encoder quality, not percentage-of-file-size:
imageQuality: 50 ≠ 50% of original bytesAvoid repeated lossy recompression. Measure bandwidth savings against CPU, battery, thermal cost, and interaction latency.
18. Process multi-select incrementally
For larger selections:
- enforce count;
- inspect cheap metadata first;
- reject incompatible/oversized items early;
- generate thumbnails lazily;
- bound transforms/uploads;
- release references no longer needed;
- expose incremental progress/cancellation when useful.
Do not
Future.waitunbounded full-byte reads or CPU-heavy transforms. Choose concurrency through profiling, not a universal number.
19. Separate acquisition from CPU-heavy processing
Picker calls belong on the normal Flutter/platform path. Potentially heavy post-processing includes:
- resizing
- encoding
- hashing
- metadata parsing
- thumbnails
- format normalization Offload pure CPU work only when profiling justifies it and the chosen library/isolate path is safe. Do not move arbitrary plugin calls into workers just because later processing is heavy.
20. Make async UI code lifecycle-safe
Picker calls cross an async gap.
Before using BuildContext or widget state after await, verify the owning UI is still mounted when required.
Do not put BuildContext inside long-lived media services.
21. Guard picker re-entry
Users can double-tap. If concurrent picker sessions would produce ambiguous state:
set picking=true
→ launch one session
→ resolve/cancel/fail
→ set picking=falseDisable or ignore duplicate requests. Do not create a global lock framework unless multiple features genuinely need coordinated ownership.
22. Keep architecture proportional
image_picker does not require a repository/use-case stack.
A thin gateway is justified when it adds:
- shared policy
- platform adaptation
- deterministic tests
- validation/persistence coordination
- substitution Otherwise direct presentation-layer use may be simpler. Do not create multiple forwarding layers that add no semantics.
Model the real UI workflow, not “async exists therefore union.” Typical state may include:
selected media
isPicking
isProcessing
isUploading
progress
validation error
upload error23. Treat media as untrusted input
Client validation improves UX; it is not the security boundary. Client may check size, count, type, dimensions, duration, format, or decodability. Server must independently enforce relevant:
- authentication/authorization
- body size
- actual media/container validity
- decoder/resource limits
- dimensions/duration
- allowed formats
- filename/path policy
- product malware/content rules Never trust extension, MIME, or a client “validated” flag as authority. Generate application-owned filenames/IDs for durable storage rather than trusting provider filenames.
24. Failure handling and telemetry
Do not swallow failures with catch (_) {}.
Map platform failures to application-level results and user-facing recovery.
Log safe metadata such as:
operation=media_pick
platform=ios
source=camera
result=failure
exception_code=...Treat cancellation separately from error telemetry.
25. Testing strategy
Unit/widget test your policy, not the plugin's system UI. Use a fake gateway to cover:
- success
- cancellation
- previous-selection preservation
- platform failure mapping
- size/type/count validation
- persistence/upload decisions
- re-entry guard
- progress/error state
- Android recovered-result handling
Use real platform integration tests for:
camera
gallery
multi/mixed selection
video
cancel
large media
orientation
HEIC/HEIF
Android lifecycle recovery
browser behavior
desktop camera capabilityTest the platforms and browsers the product actually ships.
26. Migration workflow
Before upgrading:
- inspect
image_pickerand federated package versions; - read changelogs across the upgrade range;
- confirm Flutter/Dart minimums;
- remove obsolete
PickedFile/get*usage; - review Android Photo Picker/lifecycle behavior;
- review iOS picker/performance changes;
- review web cancellation/capture changes;
- review desktop delegates;
- run unit/widget tests;
- run representative real-platform tests;
- profile any claimed performance regression at the actual slow stage.
Do not treat a federated plugin upgrade as a Dart-only change.
27. Debugging order
When media acquisition fails or becomes slow, inspect:
resolved platform package versions
→ platform configuration/capability
→ request arguments
→ cancellation vs exception
→ Android recovery
→ file lifetime/ownership
→ validation
→ preview decode
→ transformation
→ uploadExamples:
- Android result disappears after returning → inspect
retrieveLostData(). - Web fails on
File(xFile.path)→ fix native-filesystem assumption. - iOS video selection slows after upgrade → inspect resolved iOS plugin/changelog.
- OOM/jank after selection → inspect
readAsBytes()and decode dimensions before blaming picker UI.
28. Existing-code audit gates
Flag immediately:
PickedFileor removedget*APIs- no Android lost-data recovery
- picker path persisted as durable media identity
- web-shared
File(xFile.path) - video
readAsBytes() - full media bytes stored in hot UI state
- broad Android storage permission solely for selection
- previous media cleared before replacement completes
- swallowed
PlatformException - full-resolution decode for tiny preview
XFile → bytes → file → bytes- desktop camera button without capability/delegate
- type accepted solely by extension
- gallery duration trusted from
maxDuration - unbounded batch transforms/uploads
29. Production definition of done
Approve only when:
image_pickeris the correct abstraction;- SDK and resolved plugin versions were verified;
- current
XFile/pick*APIs are used; - platform setup is minimal and correct;
- Android recovery exists and is tested;
- cancellation is normal;
- prior content survives canceled replacement when appropriate;
- re-entry cannot corrupt state;
- async UI access is lifecycle-safe;
- temporary lifetime is understood;
- durable media is explicitly persisted when needed;
- web has no native path assumptions;
- desktop camera capability is explicit;
- formats are not assumed to be JPEG;
- gallery duration and important limits are independently validated;
- previews are memory-conscious;
- videos remain stream/file-backed where practical;
- huge byte arrays are absent from hot UI state;
- redundant copies are removed;
- compression/resizing is intentional;
- media is treated as untrusted;
- server validation is authoritative;
- logs avoid sensitive media metadata;
- relevant automated and real-platform tests pass.
Final engineering rule
Before writing:
final file = await ImagePicker().pickImage(...);answer:
What media does the user need?
Which platforms ship?
Is image_picker the right abstraction?
Who owns the returned media?
How long must it survive?
How large can it be?
How will it be previewed?
How will it be uploaded?
What lifecycle can interrupt the operation?
What permissions/configuration are truly required?
What security validation happens after selection?Then implement the smallest correct media pipeline.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

