diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt index 2a6d0c65..3a55ef72 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModuleImpl.kt @@ -19,7 +19,7 @@ object NativeMessagingInAppModuleImpl { private val inAppMessagingModule: ModuleMessagingInApp? get() = kotlin.runCatching { CustomerIO.instance().inAppMessaging() }.getOrNull() - val inAppEventListener = ReactInAppEventListener() + val inAppEventListener = ReactInAppEventListener.instance /** * Adds InAppMessaging module to native Android SDK based on configuration provided by customer diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt index f38fed99..e6ee6451 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/ReactInAppEventListener.kt @@ -9,7 +9,7 @@ import io.customer.messaginginapp.type.InAppMessage * React Native bridge for Customer.io in-app messaging events. * Converts native SDK events to JavaScript compatible format. */ -class ReactInAppEventListener : InAppEventListener { +class ReactInAppEventListener private constructor() : InAppEventListener { // Event emitter function to send events to React Native layer private var eventEmitter: ((ReadableMap) -> Unit)? = null @@ -30,6 +30,9 @@ class ReactInAppEventListener : InAppEventListener { actionValue: String? = null, actionName: String? = null, ) { + // Get the emitter, return early if not set + val emitter = eventEmitter ?: return + val data = buildMap { put("eventType", eventType) put("messageId", message.messageId) @@ -38,7 +41,7 @@ class ReactInAppEventListener : InAppEventListener { actionName?.let { put("actionName", it) } } - eventEmitter?.invoke(Arguments.makeNativeMap(data)) + emitter.invoke(Arguments.makeNativeMap(data)) } override fun errorWithMessage(message: InAppMessage) = emitInAppEvent( @@ -66,4 +69,9 @@ class ReactInAppEventListener : InAppEventListener { eventType = "messageShown", message = message, ) + + companion object { + // Singleton instance with public visibility for direct access by Expo plugin + val instance: ReactInAppEventListener by lazy { ReactInAppEventListener() } + } } diff --git a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt index 73de430e..49cc423f 100644 --- a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt +++ b/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt @@ -1,10 +1,8 @@ package io.customer.reactnative.sdk.logging -import android.os.Build import android.util.Log import com.facebook.react.bridge.ReactApplicationContext import io.customer.reactnative.sdk.NativeCustomerIOLoggingSpec -import io.customer.reactnative.sdk.util.onlyForLegacyArch /** * React Native module implementation for Customer.io Logging Native SDK @@ -15,14 +13,6 @@ class NativeCustomerIOLoggingModule( ) : NativeCustomerIOLoggingSpec(reactContext) { override fun getName(): String = NativeCustomerIOLoggingModuleImpl.NAME - // true if the app is currently running under armeabi/armeabi-v7a ABIs. - // We check only the first ABI in SUPPORTED_ABIS because the first one is most preferred ABI. - private val isABIArmeabi: Boolean by lazy { - Build.SUPPORTED_ABIS?.firstOrNull() - ?.lowercase() - ?.contains("armeabi") == true - } - /** * Executes the given block and logs any uncaught exceptions using Android logger to protect * against unexpected crashes and failures. @@ -36,57 +26,21 @@ class NativeCustomerIOLoggingModule( } } - /** - * Executes the given action only if the current ABI supports it. - * Skips execution on armeabi/armeabi-v7a to prevent C++ crashes on unsupported architectures. - */ - private fun runOnSupportedAbi(action: () -> Unit) { - runWithTryCatch { - if (isABIArmeabi) { - // Skip execution on armeabi-v7a to avoid known native (C++) crashes on unsupported ABIs. - // This ensures stability on lower-end or legacy devices by preventing risky native calls. - return@runWithTryCatch - } - - action() - } - } - override fun initialize() { runWithTryCatch { super.initialize() - if (isABIArmeabi) { - Log.i( - "[CIO]", - "Native logging is disabled on armeabi/armeabi-v7a ABI to avoid native crashes (Supported ABIs: ${Build.SUPPORTED_ABIS?.joinToString()})" - ) - } - runOnSupportedAbi { - NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> - emitOnCioLogEvent(data) - } + NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> + emitOnCioLogEvent(data) } } } override fun invalidate() { - runOnSupportedAbi { + runWithTryCatch { NativeCustomerIOLoggingModuleImpl.invalidate() } runWithTryCatch { super.invalidate() } } - - override fun addListener(eventName: String?) { - runOnSupportedAbi { - onlyForLegacyArch("addListener") - } - } - - override fun removeListeners(count: Double) { - runOnSupportedAbi { - onlyForLegacyArch("removeListeners") - } - } } diff --git a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt index c2624231..3024167e 100644 --- a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +++ b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt @@ -1,9 +1,7 @@ package io.customer.reactnative.sdk.messaginginapp -import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import io.customer.reactnative.sdk.NativeCustomerIOMessagingInAppSpec -import io.customer.reactnative.sdk.util.onlyForLegacyArch /** * React Native module implementation for Customer.io In-App Messaging Native SDK @@ -30,12 +28,4 @@ class NativeMessagingInAppModule( override fun dismissMessage() { NativeMessagingInAppModuleImpl.dismissMessage() } - - override fun addListener(eventName: String?) { - onlyForLegacyArch("addListener") - } - - override fun removeListeners(count: Double) { - onlyForLegacyArch("removeListeners") - } } diff --git a/src/customerio-inapp.ts b/src/customerio-inapp.ts index 88280d9d..17a03911 100644 --- a/src/customerio-inapp.ts +++ b/src/customerio-inapp.ts @@ -1,8 +1,4 @@ -import { - NativeEventEmitter, - type EventSubscription, - type TurboModule, -} from 'react-native'; +import { type EventSubscription, type TurboModule } from 'react-native'; import { InlineInAppMessageView } from './components'; import { NativeLoggerListener } from './native-logger-listener'; import NativeCustomerIOMessagingInApp, { @@ -11,22 +7,13 @@ import NativeCustomerIOMessagingInApp, { import type { InAppMessageEventType } from './types'; import { callNativeModule, ensureNativeModule } from './utils/native-bridge'; -// Constant value used for emitting all events for in-app from native modules -const InAppEventListenerEventName = 'InAppEventListener'; - /** * Ensures all methods defined in codegen spec are implemented by the public module * * @internal */ interface NativeInAppSpec - extends Omit< - CodegenSpec, - | keyof TurboModule - | 'onInAppEventReceived' - | 'addListener' - | 'removeListeners' - > {} + extends Omit {} // Reference to the native CustomerIO Data Pipelines module for SDK operations const nativeModule = ensureNativeModule(NativeCustomerIOMessagingInApp); @@ -59,16 +46,22 @@ class CustomerIOInAppMessaging implements NativeInAppSpec { return withNativeModule((native) => { try { - // Try new arch TurboModule event listener (not available in old arch) + // Register TurboModule event listener and return subscription. + // This method is generated by codegen in the native modules. + // Wrapped in try-catch due to previous reports of crashes on certain Android architectures. return native.onInAppEventReceived(emitter); - } catch { + } catch (error) { NativeLoggerListener.warn( - 'Falling back to legacy event emitter (likely using old architecture). ' + - 'Switch to new architecture for better performance and event handling.' + 'Failed to attach in-app event listener:', + error ); - // Fallback to old arch NativeEventEmitter when new arch method fails - const eventEmitter = new NativeEventEmitter(native); - return eventEmitter.addListener(InAppEventListenerEventName, emitter); + // Return a no-op subscription to maintain backwards compatibility + return { + remove: () => {}, + eventType: '', + key: 0, + subscriber: null as any, + } as EventSubscription; } }); } diff --git a/src/native-logger-listener.ts b/src/native-logger-listener.ts index 73baaa3f..e418a31f 100644 --- a/src/native-logger-listener.ts +++ b/src/native-logger-listener.ts @@ -1,4 +1,3 @@ -import { NativeEventEmitter } from 'react-native'; import NativeCustomerIOLogging from './specs/modules/NativeCustomerIOLogging'; import { CioLogLevel } from './types'; import { callNativeModule, ensureNativeModule } from './utils/native-bridge'; @@ -56,21 +55,12 @@ export class NativeLoggerListener { withNativeModule((native) => { try { - // Try new arch TurboModule log listener (not available in old arch) + // Register TurboModule event listener and return subscription. + // This method is generated by codegen in the native modules. + // Wrapped in try-catch due to previous reports of crashes on certain Android architectures. native.onCioLogEvent(logHandler); - } catch { - // Fallback to old arch NativeEventEmitter when new arch method fails - try { - // Use try-catch to prevent crashes for cases where native module may - // not be available instantly - const bridge = new NativeEventEmitter(native); - bridge.addListener('CioLogEvent', logHandler); - } catch (error) { - NativeLoggerListener.warn( - 'Failed to attach old arch log listener:', - error - ); - } + } catch (error) { + NativeLoggerListener.warn('Failed to attach log listener:', error); } }); this.isInitialized = true; diff --git a/src/specs/modules/NativeCustomerIOLogging.ts b/src/specs/modules/NativeCustomerIOLogging.ts index c8a0246c..d6842a39 100644 --- a/src/specs/modules/NativeCustomerIOLogging.ts +++ b/src/specs/modules/NativeCustomerIOLogging.ts @@ -1,6 +1,5 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native'; import type { - Double, EventEmitter, UnsafeObject, } from 'react-native/Libraries/Types/CodegenTypes'; @@ -18,11 +17,6 @@ import type { /** TurboModule interface for CustomerIO logging native operations */ export interface Spec extends TurboModule { readonly onCioLogEvent: EventEmitter; - // Old architecture support: EventEmitter requires these methods for proper functionality - /** @internal - Registers an event listener for old architecture EventEmitter */ - addListener: (eventName: string) => void; - /** @internal - Removes event listeners for old architecture EventEmitter */ - removeListeners: (count: Double) => void; } export default TurboModuleRegistry.getEnforcing( diff --git a/src/specs/modules/NativeCustomerIOMessagingInApp.ts b/src/specs/modules/NativeCustomerIOMessagingInApp.ts index 5114d420..92fc41da 100644 --- a/src/specs/modules/NativeCustomerIOMessagingInApp.ts +++ b/src/specs/modules/NativeCustomerIOMessagingInApp.ts @@ -1,6 +1,5 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native'; import type { - Double, EventEmitter, UnsafeObject, } from 'react-native/Libraries/Types/CodegenTypes'; @@ -16,11 +15,6 @@ import type { export interface Spec extends TurboModule { dismissMessage(): void; readonly onInAppEventReceived: EventEmitter; - // Old architecture support: EventEmitter requires these methods for proper functionality - /** @internal - Registers an event listener for old architecture EventEmitter */ - addListener: (eventName: string) => void; - /** @internal - Removes event listeners for old architecture EventEmitter */ - removeListeners: (count: Double) => void; } export default TurboModuleRegistry.getEnforcing(