Replace the buggy temp file polling mechanism with direct native-to-JS communication via custom Capacitor plugins for iOS and Android. This eliminates race conditions, file management complexity, and polling overhead. BREAKING CHANGE: Removes backward compatibility with temp file approach. Android minSdkVersion upgraded from 22 to 23. Changes: - iOS: Created SharedImagePlugin (CAPBridgedPlugin) and SharedImageUtility - Uses App Group UserDefaults for data sharing between extension and app - Implements getSharedImage() and hasSharedImage() methods - Removes temp file writing/reading logic from AppDelegate - Android: Created SharedImagePlugin with @CapacitorPlugin annotation - Uses SharedPreferences for data storage instead of temp files - Implements same interface as iOS plugin - Removes temp file handling from MainActivity - TypeScript: Added plugin definitions and registration - Created SharedImagePlugin interface and web fallback - Updated main.capacitor.ts to use plugin instead of Filesystem API - Removed pollForFileExistence() and related file I/O code - Android: Upgraded minSdkVersion from 22 to 23 - Required for SharedPreferences improvements and better API support Benefits: - Eliminates race conditions from file polling - Removes file cleanup complexity - Direct native-to-JS communication (no file I/O) - Better performance (no polling overhead) - More reliable data transfer between share extension and app - Cleaner architecture with proper plugin abstraction
11 KiB
Shared Image Plugin - Pre-Implementation Decision Checklist
Date: 2025-12-03
Status: Pre-Implementation Planning
Purpose: Identify and document decisions needed before implementing SharedImagePlugin
✅ Completed Decisions
1. Minimum OS Versions
- ✅ iOS: Keep at 13.0 (no changes needed)
- ✅ Android: Upgraded from API 22 to API 23 (completed)
- ✅ Rationale: Meets Capacitor 6 requirements, minimal device impact
2. Data Storage Strategy
- ✅ iOS: Use App Group UserDefaults (already implemented in Share Extension)
- ✅ Android: Use SharedPreferences (to be implemented)
- ✅ Rationale: Direct, efficient, no file I/O needed
🔍 Decisions Needed Before Implementation
1. Plugin Method Design
Decision: What methods should the plugin expose?
Options:
- Option A (Minimal): Only
getSharedImage()- read and clear in one call - Option B (Recommended):
getSharedImage()+hasSharedImage()- allows checking without reading - Option C (Extended): Add
clearSharedImage()- explicit clearing without reading
Recommendation: Option B
getSharedImage(): Returns{ base64: string, fileName: string } | nullhasSharedImage(): Returns{ hasImage: boolean }- useful for quick checks
Decision Needed: ✅ Confirm Option B or choose alternative
2. Error Handling Strategy
Decision: How should the plugin handle errors?
Options:
- Option A: Return
nullfor all errors (no shared image = no error) - Option B: Use
call.reject()for actual errors, returnnullonly when no image exists - Option C: Return error object in result:
{ error: string } | { base64: string, fileName: string }
Recommendation: Option B
getSharedImage()returnsnullwhen no image exists (normal case)call.reject()for actual errors (UserDefaults unavailable, data corruption, etc.)- Clear distinction between "no data" (normal) vs "error" (exceptional)
Decision Needed: ✅ Confirm Option B or choose alternative
3. Data Clearing Strategy
Decision: When should shared image data be cleared?
Current Behavior (temp file approach):
- Data cleared after reading (immediate)
Options:
- Option A: Clear immediately after reading (current behavior)
- Option B: Clear on next read (allow re-reading until consumed)
- Option C: Clear after successful storage in temp DB (JS confirms receipt)
Recommendation: Option A (immediate clearing)
- Prevents accidental re-reading
- Simpler implementation
- Matches current behavior
- If JS fails to store, user can share again
Decision Needed: ✅ Confirm Option A or choose alternative
4. iOS Plugin Registration
Decision: How should the iOS plugin be registered?
Options:
- Option A: Auto-discovery (Capacitor finds plugins by naming convention)
- Option B: Manual registration in AppDelegate
- Option C: Hybrid (auto-discovery with manual registration as fallback)
Recommendation: Option A (auto-discovery)
- Follows Capacitor best practices
- Less code to maintain
- Other plugins in project use auto-discovery (SafeAreaPlugin uses manual, but that's older pattern)
Note: Need to verify plugin naming convention:
- Class name:
SharedImagePlugin - File name:
SharedImagePlugin.swift - Location:
ios/App/App/SharedImagePlugin.swift
Decision Needed: ✅ Confirm Option A, or if auto-discovery doesn't work, use Option B
5. TypeScript Interface Design
Decision: What should the TypeScript interface look like?
Proposed Interface:
export interface SharedImageResult {
base64: string;
fileName: string;
}
export interface SharedImagePlugin {
getSharedImage(): Promise<SharedImageResult | null>;
hasSharedImage(): Promise<{ hasImage: boolean }>;
}
Questions:
- Should
fileNamebe optional? (Currently always provided, but could be empty string) - Should we include metadata (image size, MIME type)?
- Should
hasSharedImage()return more info (like fileName without reading)?
Recommendation: Keep simple for now:
fileNameis always a string (may be default "shared-image.jpg")- No metadata initially (can add later if needed)
hasSharedImage()only returns boolean (keep it lightweight)
Decision Needed: ✅ Confirm interface design or request changes
6. Android Data Storage Timing
Decision: When should Android store shared image data in SharedPreferences?
Current Flow:
- Share intent received in MainActivity
- Image processed and written to temp file
- JS reads temp file
New Flow Options:
- Option A: Store in SharedPreferences immediately when share intent received (in
processSharedImage()) - Option B: Store when plugin is first called (lazy loading)
- Option C: Store in both places during transition (backward compatibility)
Recommendation: Option A (immediate storage)
- Data available immediately when plugin is called
- No timing issues
- Matches iOS pattern (data stored by Share Extension)
Implementation:
- Update
processSharedImage()in MainActivity to store in SharedPreferences - Remove temp file writing
- Plugin reads from SharedPreferences
Decision Needed: ✅ Confirm Option A
7. Migration Strategy
Decision: How to handle the transition from temp file to plugin?
Options:
- Option A (Clean Break): Remove temp file code immediately, use plugin only
- Option B (Gradual): Support both approaches temporarily, remove temp file later
- Option C (Feature Flag): Use feature flag to switch between approaches
Recommendation: Option A (clean break)
- Simpler implementation
- Less code to maintain
- Temp file approach is buggy anyway (why we're replacing it)
- Can rollback via git if needed
Rollback Plan:
- Keep temp file code in git history
- If plugin has issues, can revert commit
- Test thoroughly before removing temp file code
Decision Needed: ✅ Confirm Option A
8. Plugin Naming
Decision: What should the plugin be named?
Options:
- Option A:
SharedImage(matches file/class names) - Option B:
SharedImagePlugin(more explicit) - Option C:
NativeShare(more generic, could handle other share types)
Recommendation: Option A (SharedImage)
- Matches Capacitor naming conventions (plugins are referenced without "Plugin" suffix)
- Examples:
Capacitor.Plugins.Camera,Capacitor.Plugins.Filesystem - TypeScript:
SharedImage.getSharedImage()
Decision Needed: ✅ Confirm Option A
9. iOS: Reuse getSharedImageData() or Move to Plugin?
Decision: Should the plugin reuse AppDelegate's getSharedImageData() or implement its own?
Current Code:
AppDelegate.getSharedImageData()exists and works- Reads from App Group UserDefaults
- Clears data after reading
Options:
- Option A: Plugin calls
getSharedImageData()from AppDelegate - Option B: Plugin implements its own logic (duplicate code)
- Option C: Move
getSharedImageData()to a shared utility, both use it
Recommendation: Option C (shared utility)
- DRY principle
- Single source of truth
- But: May be overkill for simple logic
Alternative Recommendation: Option B (plugin implements own logic)
- Plugin is self-contained
- No dependency on AppDelegate
- Logic is simple (just UserDefaults read/clear)
- Can remove
getSharedImageData()from AppDelegate after migration
Decision: ✅ Option C (shared utility) - CONFIRMED
- Create shared utility for reading from App Group UserDefaults
- Both AppDelegate and plugin use the shared utility
- Single source of truth for shared image data access
10. Android: SharedPreferences Key Names
Decision: What keys should be used in SharedPreferences?
Proposed Keys:
shared_image_base64- Base64 stringshared_image_file_name- File nameshared_image_ready- Boolean flag (optional, for quick checks)
Alternative:
- Use a single JSON object:
shared_image_data={ base64: "...", fileName: "..." }
Recommendation: Separate keys (first option)
- Simpler to read/write
- No JSON parsing needed
- Matches iOS pattern (separate UserDefaults keys)
- Flag is optional but useful for
hasSharedImage()
Decision Needed: ✅ Confirm key naming or request changes
11. Testing Strategy
Decision: What testing approach should we use?
Options:
- Option A: Manual testing only
- Option B: Manual + automated unit tests for plugin methods
- Option C: Manual + integration tests
Recommendation: Option A (manual testing) for now
- Plugins are hard to unit test (require native environment)
- Manual testing is sufficient for initial implementation
- Can add automated tests later if needed
Test Scenarios:
- Share image from Photos app → Verify appears in app
- Share while app backgrounded → Verify appears when app becomes active
- Share while app closed → Verify appears on app launch
- Multiple rapid shares → Verify only latest is processed
- Share then close app before processing → Verify data persists
- Share then clear app data → Verify graceful handling
Decision Needed: ✅ Confirm testing approach
12. Documentation Updates
Decision: What documentation needs updating?
Files to Update:
- ✅ Implementation plan (this document)
- ⚠️
doc/native-share-target-implementation.md- Update to reflect plugin approach - ⚠️
doc/ios-share-implementation-status.md- Mark plugin as implemented - ⚠️ Code comments in
main.capacitor.ts- Update to reflect plugin usage
Decision Needed: ✅ Confirm documentation update list
Summary of Decisions Needed
| # | Decision | Recommendation | Status |
|---|---|---|---|
| 1 | Plugin Methods | Option B: getSharedImage() + hasSharedImage() |
✅ Confirmed |
| 2 | Error Handling | Option B: null for no data, reject() for errors |
✅ Confirmed |
| 3 | Data Clearing | Option A: Clear immediately after reading | ✅ Confirmed |
| 4 | iOS Registration | Option A: Auto-discovery | ✅ Confirmed |
| 5 | TypeScript Interface | Proposed interface (see above) | ✅ Confirmed |
| 6 | Android Storage Timing | Option A: Store immediately on share intent | ✅ Confirmed |
| 7 | Migration Strategy | Option A: Clean break, remove temp file code | ✅ Confirmed |
| 8 | Plugin Naming | Option A: SharedImage |
✅ Confirmed |
| 9 | iOS Code Reuse | Option C: Shared utility | ✅ Confirmed |
| 10 | Android Key Names | Separate keys: shared_image_base64, shared_image_file_name |
✅ Confirmed |
| 11 | Testing Strategy | Option A: Manual testing | ✅ Confirmed |
| 12 | Documentation | Update listed files | ✅ Confirmed |
| - | Multiple Images | Single image only (SharedPhotoView requirement) | ✅ Confirmed |
| - | Backward Compatibility | No temp file backward compatibility | ✅ Confirmed |
Next Steps
- Review this checklist and confirm or modify recommendations
- Make decisions on all pending items
- Update implementation plan with confirmed decisions
- Begin implementation with clear specifications
Questions to Consider
- Are there any edge cases not covered?
- Should we support multiple images (currently only first image)?
- Should we add image metadata (size, MIME type) in the future?
- Do we need backward compatibility with temp file approach?
- Should plugin methods be synchronous or async? (Capacitor plugins are async by default)