Skip to content

fix(ios): resolve Rokt placeholders via RCTViewRegistry - #377

Merged
thomson-t merged 2 commits into
mainfrom
fix/ios-rokt-placeholder-view-resolution
Aug 21, 2026
Merged

fix(ios): resolve Rokt placeholders via RCTViewRegistry#377
thomson-t merged 2 commits into
mainfrom
fix/ios-rokt-placeholder-view-resolution

Conversation

@thomson-t

Copy link
Copy Markdown
Contributor

Summary

selectPlacements routed its only native call through [self.bridge.uiManager addUIBlock:]. That path is no longer viable on the New Architecture:

  • React Native 0.84 defaults RCT_REMOVE_LEGACY_ARCH=1 (scripts/react_native_pods.rb:93), under which RCTUIManager's addUIBlock: compiles to an empty method body — the block never runs.
  • In bridgeless mode self.bridge is an RCTBridgeProxy, whose RCTUIManagerProxy logs "This method isn't implemented faithfully. Please migrate to RCTViewRegistry" (silenced at the default log level).
  • The existing nil-bridge check was log-only with no early return, so the call was messaged to nil and discarded — no RoktEvent, no promise rejection, no diagnostic. Callers only saw a placement that never settled.
  • methodQueue returned self.bridge.uiManager.methodQueue, which is nil on the proxy, so React Native silently substituted a shared background queue — the declared "run on the UIManager queue" intent had not held for some time.

Changes:

  • Resolve placeholders through RCTViewRegistry (@synthesize viewRegistry_DEPRECATED), which React Native populates in both bridge and bridgeless modes via RCTBridgeModuleDecorator. This is what React Native's own core modules and react-native-maps / -screens / -svg use.
  • Dispatch with RCTExecuteOnMainQueue; methodQueue now returns the main queue, matching Android's UiThreadUtil.runOnUiThread (MPRoktModule.kt). The bridge property is removed.
  • Reject non-numeric react tags up front: the spec allows number | null and viewForReactTag: throws on NSNull, where the old dictionary subscript returned nil.
  • Sample app: set dependencyProvider, required since React Native 0.76. Without it RCTReactNativeFactory reports no third-party Fabric components, so RoktNativeLayout was never registered, <RoktLayoutView> mounted as RCTUnimplementedViewComponentView and embedded placements resolved no placeholder view.

Behaviour is otherwise unchanged — unresolvable placeholders still log via RCTLogError and are skipped. Emitting a PlacementFailure on those paths instead of staying silent is deliberately left for a follow-up.

Testing Plan

New sample/ios/MParticleSampleTests/RNMPRoktPlaceholderTests.m (5 tests) drives resolvePlaceholders: against a real RCTViewRegistry with a bridgeless component-view provider — the same hook RCTInstance wires to the surface presenter. Covers the registry wiring plus every skip branch: unmounted tag, wrong view class, non-numeric tag, and empty placeholders.

Verified on the sample app, iPhone 17 Pro / iOS 26.5, New Architecture:

before after
overlay placement reaches calling mParticle Core selectPlacements same, with 0 addUIBlock log lines
embedded placement 1 placeholder(s)resolved 0 1 placeholder(s)resolved 1
RoktFabricWrapperView initialized never logged logged on mount

The before/after was run by swapping only RNMPRokt.{h,mm} between builds with identical JS. yarn test and yarn lint pass; iOS suites RNMPRoktPlaceholderTests + RCTConvertCommerceMappingTests are 12/12.

Additional testing worth doing:

  • An old-architecture build to exercise the #else branches (RCTViewRegistry covers both, but this was not built here).
  • A build with RCT_REMOVE_LEGACY_ARCH=0.
  • Android regression check — untouched by this change, but the two platforms now share the same threading and view-resolution shape.

Master Issue

N/A — no linked work item.

selectPlacements routed its only native call through
[self.bridge.uiManager addUIBlock:]. That path is no longer viable on the New
Architecture:

- React Native 0.84 defaults RCT_REMOVE_LEGACY_ARCH=1, under which
  RCTUIManager's addUIBlock: compiles to an empty method body, so the block
  never runs.
- In bridgeless mode self.bridge is an RCTBridgeProxy whose RCTUIManagerProxy
  logs "This method isn't implemented faithfully. Please migrate to
  RCTViewRegistry".
- The existing nil-bridge check only logged; it had no early return, so the
  call was messaged to nil and discarded with no RoktEvent, no promise
  rejection and no diagnostic. Callers only saw a placement that never
  settled.
- methodQueue returned self.bridge.uiManager.methodQueue, which is nil on the
  proxy, so React Native silently substituted a shared background queue.

Resolve placeholders through RCTViewRegistry instead, which React Native
populates in both bridge and bridgeless modes, and dispatch with
RCTExecuteOnMainQueue. methodQueue now returns the main queue, matching
Android's UiThreadUtil.runOnUiThread, and the bridge property is removed.
Non-numeric react tags are rejected up front because the spec allows
`number | null` and viewForReactTag: would throw on NSNull, where the old
dictionary subscript returned nil.

Also set dependencyProvider in the sample app. It has been required since
React Native 0.76; without it RCTReactNativeFactory reports no third-party
Fabric components, so RoktNativeLayout was never registered, <RoktLayoutView>
mounted as RCTUnimplementedViewComponentView and embedded placements resolved
no placeholder view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 21, 2026 18:23
@thomson-t
thomson-t requested a review from a team as a code owner August 21, 2026 18:23
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches native view lookup and main-thread dispatch for Rokt placements, so a regression can silently drop embedded ads. Scope is iOS-only and skip-on-error behavior is unchanged.

Overview
Fixes iOS Rokt embedded placements never running on New Architecture / RN 0.84, where [bridge.uiManager addUIBlock:] is a no-op and self.bridge is often nil.

selectPlacements now looks up views via RCTViewRegistry (viewRegistry_DEPRECATED) and runs on the main queue with RCTExecuteOnMainQueue, matching Android. Non-numeric tags (null) are skipped instead of throwing. The bridge property is removed.

The sample app sets dependencyProvider so Fabric registers RoktLayoutView. Adds RNMPRoktPlaceholderTests covering registry wiring and skip paths.

Reviewed by Cursor Bugbot for commit 4126215. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the iOS Rokt integration to resolve embedded placement placeholder views via RCTViewRegistry (instead of RCTUIManager addUIBlock:), making selectPlacements work correctly under React Native New Architecture / bridgeless mode, and adds sample + test coverage to validate the new resolution path.

Changes:

  • iOS: switch placeholder resolution to RCTViewRegistry and dispatch to the main queue; remove reliance on bridge.uiManager.
  • iOS tests: add bridgeless RCTViewRegistry-backed unit tests covering successful registry wiring and all placeholder-skip branches.
  • Sample app: set dependencyProvider so third-party Fabric components (including RoktNativeLayout) register correctly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sample/ios/MParticleSampleTests/RNMPRoktPlaceholderTests.m Adds unit tests for placeholder resolution against a real RCTViewRegistry in bridgeless mode.
sample/ios/MParticleSample/AppDelegate.mm Registers RCTAppDependencyProvider to ensure Fabric components are discoverable in the sample app.
sample/ios/MParticleSample.xcodeproj/project.pbxproj Wires the new test file into the sample test target.
ios/RNMParticle/RNMPRokt.mm Replaces addUIBlock: with RCTViewRegistry-based placeholder resolution on the main thread.
ios/RNMParticle/RNMPRokt.h Removes the bridge property from the New Architecture interface.
Suppressed comments (1)

ios/RNMParticle/RNMPRokt.mm:345

  • The error log prints the placeholder key as the tag (#%@, where key is the placeholder location string). Logging the actual reactTag (and optionally the placeholder key) will make it clear which tag failed to resolve.
            RCTLogError(@"Cannot find RoktEmbeddedView with tag #%@", key);

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ios/RNMParticle/RNMPRokt.mm Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@nickolas-dimitrakas nickolas-dimitrakas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thomson-t
thomson-t merged commit 1ad4da1 into main Aug 21, 2026
11 checks passed
@thomson-t
thomson-t deleted the fix/ios-rokt-placeholder-view-resolution branch August 21, 2026 21:02
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.

3 participants