Files
crowd-funder-for-time-pwa/doc/shared-image-plugin-pre-implementation-decisions.md
Jose Olarte III 84983ee10b refactor(shared-image): replace temp file approach with native Capacitor plugins
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
2025-12-04 18:03:47 +08:00

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 } | null
  • hasSharedImage(): 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 null for all errors (no shared image = no error)
  • Option B: Use call.reject() for actual errors, return null only when no image exists
  • Option C: Return error object in result: { error: string } | { base64: string, fileName: string }

Recommendation: Option B

  • getSharedImage() returns null when 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 fileName be 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:

  • fileName is 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:

  1. Share intent received in MainActivity
  2. Image processed and written to temp file
  3. 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 string
  • shared_image_file_name - File name
  • shared_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:

  1. Share image from Photos app → Verify appears in app
  2. Share while app backgrounded → Verify appears when app becomes active
  3. Share while app closed → Verify appears on app launch
  4. Multiple rapid shares → Verify only latest is processed
  5. Share then close app before processing → Verify data persists
  6. 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

  1. Review this checklist and confirm or modify recommendations
  2. Make decisions on all pending items
  3. Update implementation plan with confirmed decisions
  4. 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)