Skip to content

fix(android): use bridge.registerForActivityResult for Fragment compatibility - #84

Merged
jcesarmobile merged 1 commit into
ionic-team:mainfrom
omarihamza:fix/android-portals-activityresult-bridge-registration
Oct 16, 2026
Merged

jcesarmobile merged 1 commit into
ionic-team:mainfrom
omarihamza:fix/android-portals-activityresult-bridge-registration

Conversation

@omarihamza

Copy link
Copy Markdown
Contributor

Summary

CameraPlugin fails to register during Bridge initialization on Android when hosted inside Ionic Portals, making camera features completely non-functional in Portals flows. iOS is unaffected.

Sibling fix of ionic-team/capacitor-geolocation#104.

IonCameraFlow.setupLaunchers() registers its six ActivityResult launchers directly against the Activity. AndroidX only allows registerForActivityResult while the owner's lifecycle is ≤ CREATED. In a stock BridgeActivity the Bridge is built in the Activity's onCreate() (state CREATED), so this is legal. Under Portals the Bridge is built in PortalFragment.onViewCreated(), by which point the host Activity is already STARTED/RESUMED, so AndroidX throws and Capacitor masks it as a PluginLoadException.

Root cause

NativePlugin com.capacitorjs.plugins.camera.CameraPlugin failed to load
com.getcapacitor.PluginLoadException: Unable to load plugin instance. Ensure plugin is publicly accessible
    at com.getcapacitor.PluginHandle.load(PluginHandle.java:107)
    ...
Caused by:
java.lang.IllegalStateException: LifecycleOwner <host activity> is attempting to register
while current state is RESUMED. LifecycleOwners must call register before they are STARTED.

Capacitor already exposes Bridge.registerForActivityResult(...), which registers against the hosting Fragment when one exists (the PortalFragment, still CREATED during onViewCreated) and against the Activity otherwise. Every non-rewritten Capacitor plugin uses this path — which is why only the ion*-based plugins fail.

Fix

Change the registration target from the Activity to Capacitor's Bridge for all six launchers in IonCameraFlow.setupLaunchers() (cameraLauncher, cameraCropLauncher, videoLauncher, galleryLauncher, galleryCropLauncher, editLauncher):

-        cameraLauncher = activity.registerForActivityResult(
+        cameraLauncher = bridge.registerForActivityResult(
             ActivityResultContracts.StartActivityForResult()
         ) { result ->
             handleCameraResult(result)
         }

IonCameraFlow already receives bridge: Bridge in its constructor, so no signature change is needed. Contracts, callbacks, and runtime behavior are identical — only the lifecycle owner of the registration changes. Under a standard BridgeActivity, Bridge.registerForActivityResult resolves to the Activity, so existing hosting is unaffected.

Steps to reproduce

  1. Host a Portal in a PortalFragment where the containing Activity is already STARTED/RESUMED when the Portal loads.
  2. Register @capacitor/camera.
  3. Load the Portal and watch Logcat during PortalFragment.onViewCreated → Bridge init.
  4. The plugin fails to register with the trace above; camera features do nothing.

Testing

  • npm run verify:android (./gradlew clean build test) — passes.

Notes

  • Separately, it would help to improve PluginLoadException in capacitor core to chain the underlying exception — the current "Ensure plugin is publicly accessible" message obscures the real cause and initially misdirects debugging.

…rtals compatibility

IonCameraFlow.setupLaunchers() registered its six ActivityResult
launchers directly against the Activity. Under Ionic Portals the Bridge
is built in PortalFragment.onViewCreated() when the host Activity is
already STARTED/RESUMED, so AndroidX's registerForActivityResult guard
throws IllegalStateException and the plugin fails to load (masked by
Capacitor as PluginLoadException).

Register against Capacitor's Bridge instead, which resolves to the
hosting Fragment (still CREATED) under Portals and to the Activity
otherwise. The contracts, callbacks, and runtime behavior are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jcesarmobile jcesarmobile changed the title fix(android): register ActivityResult launchers against Bridge for Ionic Portals compatibility fix(android): use bridge.registerForActivityResult for Fragment compatibility Oct 16, 2026
@jcesarmobile
jcesarmobile merged commit 41ece15 into ionic-team:main Oct 16, 2026
8 checks passed
capacitor-bot pushed a commit that referenced this pull request Oct 16, 2026
## [8.2.6](v8.2.5...v8.2.6) (2026-10-16)

### Bug Fixes

* **android:** use bridge.registerForActivityResult for Fragment compatibility ([#84](#84)) ([41ece15](41ece15))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants