Skip to content

Add an iOS withDangerousMod to the Expo config plugin that patches RCTThirdPartyComponentsProvider.mm#176

Open
Th0mYT wants to merge 44 commits into
azesmway:mainfrom
Th0mYT:main
Open

Add an iOS withDangerousMod to the Expo config plugin that patches RCTThirdPartyComponentsProvider.mm#176
Th0mYT wants to merge 44 commits into
azesmway:mainfrom
Th0mYT:main

Conversation

@Th0mYT
Copy link
Copy Markdown

@Th0mYT Th0mYT commented Feb 20, 2026

What was changed: plugin/src/index.ts (and its compiled output plugin/build/index.js)

What was broken: Expo's prebuild ran the C++ codegen for unityview but never generated the Fabric registration step. RNUnityViewCls() was defined in RNUnityView.mm but never
called, so Fabric couldn't find the component by name, fell back to the legacy interop layer, and updateProps was never dispatched.

The fix — withIosFabricRegistration: A new iOS withDangerousMod that runs at the end of every npx expo prebuild and patches RCTThirdPartyComponentsProvider.mm with two insertions:

// 1. Forward declaration (before @implementation)
Class RNUnityViewCls(void);

// 2. Dictionary entry (inside thirdPartyFabricComponents)
@"RNUnityView" : RNUnityViewCls(),

Call chain after the fix (matches what should happen):
codegenNativeComponent('RNUnityView')
→ Fabric finds RNUnityView via RCTThirdPartyComponentsProvider
→ initWithFrame: (lambda set, initUnityModule called as belt-and-suspenders)
→ updateEventEmitter:
→ updateProps: ← initUnityModule() ← Unity starts ✓

The initUnityModule call in initWithFrame: from the previous commit remains — it acts as a safety net for any edge case where updateProps might still be delayed.

- Detect Expo projects automatically via app.json
- Use correct framework search paths for both Expo and RN CLI
- Add Expo config plugin for automatic setup
- Update README with Expo-specific instructions

Fixes: Unity framework not found in Expo projects
@azesmway
Copy link
Copy Markdown
Owner

azesmway commented Mar 2, 2026

@Th0mYT Thanks for the improvements!
Can I take a look at everything and publish it?

@Th0mYT
Copy link
Copy Markdown
Author

Th0mYT commented Mar 2, 2026

Can I take a look at everything and publish it?

@azesmway of course. If you want, I'm working on the fork to further optimize the package for both Android and iOS. I'm also updating the plugin for expo. If you want, after finishing these changes and testing everything I can create a new PR with these changes.

@azesmway
Copy link
Copy Markdown
Owner

azesmway commented Mar 3, 2026

Can I take a look at everything and publish it?

@azesmway of course. If you want, I'm working on the fork to further optimize the package for both Android and iOS. I'm also updating the plugin for expo. If you want, after finishing these changes and testing everything I can create a new PR with these changes.

ok

@Th0mYT Th0mYT marked this pull request as draft March 3, 2026 11:02
@Th0mYT
Copy link
Copy Markdown
Author

Th0mYT commented Mar 3, 2026

Can I take a look at everything and publish it?

@azesmway of course. If you want, I'm working on the fork to further optimize the package for both Android and iOS. I'm also updating the plugin for expo. If you want, after finishing these changes and testing everything I can create a new PR with these changes.

ok

I updated the PR.

Summary of changes

ios/RNUnityView.mm — New Architecture fix

  • Unity is now initialized immediately inside initWithFrame: without waiting for updateProps to be called.
    This fixes a regression on New Arch where the Unity view would remain blank if no props were passed on mount.

android/.../UPlayer.java — Type cast fix

  • Fixed an unsafe cast that caused a compilation warning/error on some setups:
    return unityPlayer → return (FrameLayout)(Object) unityPlayer

plugin/src/index.ts — Expo config plugin refactor

Several issues fixed to make the plugin safe to run multiple times (e.g. after repeated npx expo prebuild):

  • withProjectBuildGradleMod: now strips existing flatDir / unityLibrary entries before re-inserting, preventing duplicate lines on every prebuild.
  • withSettingsGradleMod: same approach — filters out any existing unityLibrary lines before appending, so the include is always present exactly once.
  • withGradlePropertiesMod: checks if unityStreamingAssets property already exists before pushing it, avoiding duplicate gradle properties.
  • withStringsXMLMod: renamed internal variable from config to modConfig to avoid shadowing the outer config parameter.
  • withAndroidManifestMod (new): adds xmlns:tools to the manifest root and sets tools:replace="android:enableOnBackInvokedCallback" on the tag. This resolves a manifest merger conflict that occurs because unityLibrary declares this attribute with a different value.
  • withIosFabricRegistration (new): patches RCTThirdPartyComponentsProvider.mm (generated by expo prebuild) to register RNUnityView with Fabric's component registry. Without this, updateProps is never dispatched on New Arch iOS. The patch is idempotent and skips gracefully if the file doesn't exist yet.
  • Removed the unused name option from the plugin config signature.

tsconfig.json — Plugin path alias

  • Added @azesmway/react-native-unity/plugin → ./plugin/src/index to the path aliases so the plugin source can be resolved correctly during development.

package.json — Build setup for the plugin

  • Added app.plugin.js and plugin/build to the files array so the compiled plugin is included when the package is published.
  • Added build:plugin script: tsc --project plugin/tsconfig.json
  • Updated prepare to also run build:plugin after bob build.
  • Updated clean to also wipe plugin/build.

@Th0mYT Th0mYT marked this pull request as ready for review March 3, 2026 11:16
@Th0mYT Th0mYT marked this pull request as draft March 3, 2026 11:18
@Th0mYT Th0mYT marked this pull request as draft March 3, 2026 11:18
@Th0mYT Th0mYT marked this pull request as ready for review March 3, 2026 11:19
Th0mYT added 21 commits March 3, 2026 18:12
…oid devices

- Set `android:enableOnBackInvokedCallback` to `false` in the manifest.
- Use `tools:replace` to override conflicting value set by `unityLibrary`.
- Avoid crashes caused by `OnBackInvokedCallback` on pre-API 33 devices.
- Post `onReady()` calls to the main thread to prevent view hierarchy updates off the main thread.
- Wrap `sendMessageToMobileApp` event dispatch in a main thread handler.
…ensions

- Post a forced layout runnable to address issues with parent views
…chitecture

- Add fallback using OnGlobalLayoutListener to handle delayed group dimensions.
- Use ThemedReactContext in view creation for proper surface association.
- Update dispatchEvent logic to retrieve surfaceId directly from ThemedReactContext if parent chain fails.
- Bump package version to 1.0.17.
- Reset z-elevation to make Unity visible after re-parenting.
- Add delayed resume to prevent black screen on remount by allowing surface creation.
- Bump package version to 1.0.18.
…arenting

- Introduce direct SurfaceView surfaceCreated hook for precise Unity resume timing.
- Add fallback for cases without SurfaceView by implementing a delayed resume.
- Refactor surface detection into a new helper method for cleaner logic.
- Adjust z-elevation for proper Unity visibility after re-parenting.
- Add precise surface resume logic using SurfaceView and OnPreDrawListener.
- Refactor resume timing to account for Choreographer and compositor updates.
- Add pause logic in `onDetachedFromWindow` to stop rendering frames before backgrounding.
- Prevents "BufferQueueProducer disconnect: not connected" error.
…ompatibility

- Handle FLAG_FULLSCREEN deprecation with conditional logic.
- Add detailed logging for UnityPlayer constructor resolution to improve debugging.
- Catch and log alignment errors on Unity native library loading.
- Enhance robustness of UnityPlayer initialization.
…ment adjustments

- Add a 2-second timeout fallback to resume Unity if surfaceCreated never fires.
- Ensure proper frame measurement before layout to prevent missed surfaceCreated events.
- Improve logging for better debugging of surface and group layout issues.
@Th0mYT Th0mYT marked this pull request as draft March 23, 2026 09:18
@Th0mYT
Copy link
Copy Markdown
Author

Th0mYT commented Mar 23, 2026

Hi @azesmway!

I've updated the PR with several improvements focused on stability and Android compatibility.
Here's a summary of what was addressed:

Android surface & rendering fixes

  • Fixed black screens after re-parenting the Unity view by improving surface handling
  • Added a timeout fallback and measurement adjustments to surface initialization
  • Unity rendering is now paused before surface destruction to prevent crashes
  • Improved resume timing to ensure the rendering surface is ready before Unity restarts

New Architecture (Fabric) compatibility

  • Fixed layout handling and event dispatch for the new React Native architecture
  • Force layout pass to ensure valid dimensions in Fabric mode
  • Thread safety improvements for Unity callbacks and event dispatch

Android API 30+ compatibility

  • Updated UnityPlayer instantiation and flag handling for API 30+
  • Disabled predictive back gesture to prevent crashes on older devices
  • Wrapped getSurfaceId in try-catch for safer handling

Other

  • Removed jcenter() (shut down) from repositories block
  • Expo compatibility improvements for iOS Unity framework integration

Happy to discuss any of the changes or provide more context if needed!

@Th0mYT Th0mYT marked this pull request as ready for review March 23, 2026 09:26
@Th0mYT
Copy link
Copy Markdown
Author

Th0mYT commented May 14, 2026

Hi @azesmway!

have you had a chance to review this PR? Do we have the ability to move all these changes to the main repository?

Best regards!

@Th0mYT Th0mYT marked this pull request as draft May 14, 2026 10:10
HIGH — iOS pauseUnity: fixed BOOL* → BOOL in header,
implementation, export, and callsites
HIGH — Memory leak: free strdup strings and argv array
after runEmbeddedWithArgc
HIGH — CircleCI: update circleci/node:10 → cimg/node:18.20
MEDIUM — Removed stale unity global; resolve view from
registry in all manager methods
MEDIUM — onPlayerUnload/onPlayerQuit pass @{} not nil
MEDIUM — Gate unload on !androidKeepPlayerMounted
MEDIUM — Replace literal Google Maps key with placeholder
and inject via local.properties
LOW — Remove dead sharedInstance / always-nil appLaunchOpts
LOW — Nil out onUnityMessage in prepareForRecycle
LOW — Declare NativeCallProxy api as __weak
LOW — Bump GitHub Actions from @V3@v4
@Th0mYT Th0mYT marked this pull request as ready for review May 14, 2026 10:15
@Th0mYT
Copy link
Copy Markdown
Author

Th0mYT commented May 14, 2026

@azesmway @am-koan all findings from the audit have been addressed — could you do a follow-up pass to verify the fixes?

Here's what was applied:

High

  • pauseUnity: signature corrected from BOOL * to BOOL in RNUnityView.h (both arch branches), RNUnityView.mm, and RNUnityViewManager.mm. resumeUnity no longer casts false to a null pointer — it now calls [view pauseUnity:NO]
  • Memory leak in initUnityModule fixed — strdup/malloc allocations are freed after runEmbeddedWithArgc:argv:appLaunchOpts:
  • CircleCI image updated from circleci/node:10 → cimg/node:18.20

Medium

  • All exported methods (postMessage, pauseUnity, resumeUnity, unloadUnity) in RNUnityViewManager.mm now call through the view resolved from the registry, not the stale unity file-global (which has also been removed)
  • onPlayerUnload and onPlayerQuit now pass @{} instead of nil
  • componentWillUnmount in UnityView.tsx now respects androidKeepPlayerMounted before calling unloadUnity

Low

  • Dead sharedInstance and always-nil appLaunchOpts globals removed from RNUnityView.mm
  • prepareForRecycle now nils out self.onUnityMessage before unloading, preventing the stale Fabric lambda from dereferencing a freed _eventEmitter
  • NativeCallProxy global api changed to __weak so it no longer retains the view after unload
  • GitHub Actions updated from deprecated @V3@v4 (checkout, cache, setup-node, setup-java)

Additional finding (not in original audit)

  • Hardcoded Google Maps API key AIzaSyD_XHpYQ8QGYUYnVldanNhhVreAaxk3S9s found in example/android/app/src/main/AndroidManifest.xml — present since the initial commit. Replaced with a ${GOOGLE_MAPS_API_KEY} manifest placeholder injected from local.properties (gitignored). The exposed key should be revoked in Google

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