From 1df20728ef6955ffb2a8f8e848036bfa74b52d86 Mon Sep 17 00:00:00 2001 From: Yaswanth Polisetti Date: Sat, 24 Oct 2026 14:26:26 +0530 Subject: [PATCH] fix: merchant views on the New Architecture below RN 0.82 Android: below RN 0.82, createMerchantView called getReactNativeHost().getReactInstanceManager() before checking IS_NEW_ARCHITECTURE_ENABLED, so bridgeless apps whose ReactNativeHost throws there never got merchant views, and the stock host built an unused legacy ReactInstanceManager. New Architecture builds now always use the ReactHost.createSurface path with the activity context; createReactSurfaceView is removed. iOS: the RCTReactNativeFactory lookup only checked HyperSdkReactDelegate and ivars declared on the app delegate's own class, so RCTAppDelegate subclasses fell back to RCTRootView initWithBridge:, which renders nothing in bridgeless mode. Also ask the delegate for reactNativeFactory, and declare the runtime-resolved selectors so -Wundeclared-selector stays quiet. README: document newArchEnabled for Android below RN 0.82 and how merchant views find the ReactHost and RCTReactNativeFactory on the New Architecture. --- README.md | 16 +++++++++++-- .../hypersdkreact/HyperSdkReactModule.java | 24 +++---------------- ios/latest/HyperMerchantView.mm | 13 ++++++++++ 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ab9fa85e..1d335ebc 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ buildscript { - You can also provide an override for base SDK version present in plugin (the newer version among both would be considered). - Optional - Exclude microSDKs provided with HyperSDK for given clientId by adding excludedMicroSDKs - Optional +**New Architecture:** on React Native 0.82 and above the SDK always uses the New Architecture. Below 0.82 it reads the unscoped `newArchEnabled` property of your root Gradle project (not `react.newArchEnabled`), so if your app enables the New Architecture, set it in `android/gradle.properties`: + +```properties +newArchEnabled=true +``` + ### iOS Run the following command inside the ios folder of your react native project: @@ -459,8 +465,14 @@ If your view dynamically computes height. Height can be obtained by adding the f ``` -#### If your AppDelegate is in swift -If your `AppDelegate` is in `swift` and you are using react native version greater than or equal to 0.78, then your AppDelegate.swift has to implement the protocol `HyperSdkReactDelegate` and return the `reactNativeFactory` property from the `getReactNativeFactory` function. This helps us to render your view(Merchant View) in our payment page. +#### Merchant views on the New Architecture + +On the New Architecture, merchant views are rendered as surfaces of your app's existing React Native instance: + +- **Android:** through `ReactApplication.getReactHost()`, which must return the host your app runs on. Below React Native 0.82 this also requires `newArchEnabled=true` (see [Android installation](#android)). +- **iOS (React Native 0.78+):** through your app's `RCTReactNativeFactory`. The SDK looks for it on your app delegate (`UIApplication.shared.delegate`) and finds it automatically when the delegate subclasses `RCTAppDelegate` or itself declares a stored property named `reactNativeFactory`, as the React Native template does. Otherwise (for example a `lazy`, computed or inherited Swift property, or a factory kept on another object), implement `HyperSdkReactDelegate` on the app delegate and return the factory from `getReactNativeFactory` (`- (id)getReactNativeFactory` in Objective-C). The protocol takes precedence over the automatic lookup, so implementing it is always safe. Without a factory the SDK falls back to the bridge, and merchant views render empty in bridgeless mode. + +Swift: ```swift ... diff --git a/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactModule.java b/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactModule.java index 4311d997..f4e902b8 100644 --- a/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactModule.java +++ b/android/src/main/java/in/juspay/hypersdkreact/HyperSdkReactModule.java @@ -399,35 +399,17 @@ private Object getReactHostOrInstanceManager() { } - public View createReactSurfaceView(String viewName) { - try { - Class reactSurfaceClass = Class.forName("com.facebook.react.interfaces.fabric.ReactSurface"); - Object host = getReactHostOrInstanceManager(); - if (host == null) { - return null; - } - Object surface = host.getClass().getMethod("createSurface", Context.class, String.class, Bundle.class).invoke(host, context, viewName, null); - - reactSurfaceClass.getMethod("start").invoke(surface); - return (View) reactSurfaceClass.getMethod("getView").invoke(surface); - - } catch (Exception e) { - return null; - } - } - private View createMerchantView(String viewName) { - if (!useNewApprochForMerchantView) { + // Bridgeless apps may not support getReactInstanceManager() (custom hosts can throw), + // so the bridge is only touched on the old architecture. + if (!useNewApprochForMerchantView && !newArchEnabled) { if (reactInstanceManager == null) { Application app = activity.getApplication(); if (app instanceof ReactApplication) { reactInstanceManager = ((ReactApplication) app).getReactNativeHost().getReactInstanceManager(); } } - if (newArchEnabled) { - return createReactSurfaceView(viewName); - } if (reactInstanceManager == null) { return null; } diff --git a/ios/latest/HyperMerchantView.mm b/ios/latest/HyperMerchantView.mm index 8103064e..9113e46d 100644 --- a/ios/latest/HyperMerchantView.mm +++ b/ios/latest/HyperMerchantView.mm @@ -15,6 +15,13 @@ #define HAS_NEW_ARCH_SUPPORT 0 #endif +// Selectors resolved at runtime on the app delegate and the factory, declared so that +// -Wundeclared-selector stays quiet without importing the deprecated RCTAppDelegate.h +@protocol HyperReactNativeFactoryLookup +- (id)reactNativeFactory; +- (id)rootViewFactory; +@end + @implementation HyperMerchantView + (UIView *)createReactNativeViewWithModuleName:(NSString *)moduleName { @@ -26,6 +33,12 @@ + (UIView *)createReactNativeViewWithModuleName:(NSString *)moduleName { factory = [reactDelegate getReactNativeFactory]; } + // RCTAppDelegate subclasses inherit the reactNativeFactory property; its ivar lives in the + // superclass, so the ivar scan below cannot see it + if (!factory && [appDelegate respondsToSelector:@selector(reactNativeFactory)]) { + factory = [appDelegate performSelector:@selector(reactNativeFactory)]; + } + if (!factory) { unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList([appDelegate class], &ivarCount);