diff --git a/android/build.gradle b/android/build.gradle index 2178160d..53eb7495 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,6 +15,7 @@ buildscript { apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: "com.facebook.react" def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['customerio.reactnative.' + name] @@ -24,32 +25,12 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['customerio.reactnative.' + name]).toInteger() } -def isNewArchitectureEnabled() { - // Allow customers to override new architecture setting specifically for Customer.io SDK. - // This is useful as a workaround for apps where the new architecture has issues. - // Customers can add 'customerioNewArchEnabled=false' in their gradle.properties - // to force the SDK to use old architecture even when their app has newArchEnabled=true. - // This can be helpful for customers who are using the new architecture in their app, but are on older - // versions of React Native than the one used by the SDK to generate the codegen files. - if (project.hasProperty("customerioNewArchEnabled")) { - return project.customerioNewArchEnabled == "true" - } - // Otherwise, use react-native's newArchEnabled property. - return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -} - -if (isNewArchitectureEnabled()) { - apply plugin: "com.facebook.react" -} - android { namespace = 'io.customer.reactnative.sdk' compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') defaultConfig { - def isNewArchEnabled = isNewArchitectureEnabled() minSdkVersion getExtOrIntegerDefault('minSdkVersion') targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') - buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchEnabled.toString()) } buildTypes { @@ -64,16 +45,6 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } - sourceSets { - main { - if (isNewArchitectureEnabled()) { - // Include both new architecture source directories and codegen generated files - java.srcDirs += ['src/newarch'] - } else { - java.srcDirs += ['src/oldarch'] - } - } - } } repositories { diff --git a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt new file mode 100644 index 00000000..569df330 --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt @@ -0,0 +1,78 @@ +package io.customer.reactnative.sdk + +import com.facebook.react.BaseReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.uimanager.ViewManager +import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModule +import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager +import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule +import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule +import io.customer.reactnative.sdk.util.assertNotNull + +/** + * React Native package for Customer.io SDK that registers all TurboModules and ViewManagers. + * Implements new architecture support for React Native. + */ +class CustomerIOReactNativePackage : BaseReactPackage() { + /** + * Creates the list of view managers for the Customer.io React Native SDK. + */ + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return listOf(InlineInAppMessageViewManager()) + } + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + // Debugging reveals that this method is never called for ViewManagers. + // But since ReactNative docs recommend overriding it, we do so here for ViewManagers. + // See: https://reactnative.dev/docs/fabric-native-components-introduction?platforms=android#4-write-the-reactwebviewpackage + return when (name) { + InlineInAppMessageViewManager.NAME -> InlineInAppMessageViewManager() + NativeCustomerIOLoggingModule.NAME -> NativeCustomerIOLoggingModule(reactContext) + NativeCustomerIOModule.NAME -> NativeCustomerIOModule(reactContext = reactContext) + NativeMessagingInAppModule.NAME -> NativeMessagingInAppModule(reactContext) + NativeMessagingPushModule.NAME -> NativeMessagingPushModule(reactContext) + else -> assertNotNull(value = null) { "Unknown module name: $name" } + } + } + + /** + * Creates a ReactModuleInfo for React module registration with the given configuration. + * Using positional arguments instead of named arguments as named args break on RN 0.76. + */ + private fun createReactModuleInfo( + name: String, + className: String = name, + canOverrideExistingModule: Boolean = false, + needsEagerInit: Boolean = false, + isCxxModule: Boolean = false, + isTurboModule: Boolean = true, + ) = ReactModuleInfo( + name, + className, + canOverrideExistingModule, + needsEagerInit, + isCxxModule, + isTurboModule, + ) + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + // List of all Fabric ViewManagers and TurboModules registered in this package. + // Used by React Native to identify and instantiate the modules. + val moduleNames: List = listOf( + InlineInAppMessageViewManager.NAME, + NativeCustomerIOLoggingModule.NAME, + NativeCustomerIOModule.NAME, + NativeMessagingInAppModule.NAME, + NativeMessagingPushModule.NAME, + ) + return ReactModuleInfoProvider { + // Register all ViewManagers and TurboModules + moduleNames.associateWith { moduleName -> + createReactModuleInfo(name = moduleName) + } + } + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt deleted file mode 100644 index 03d85409..00000000 --- a/android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackageImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager -import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModule -import io.customer.reactnative.sdk.logging.NativeCustomerIOLoggingModuleImpl -import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModuleImpl -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModuleImpl -import io.customer.reactnative.sdk.util.assertNotNull - -/** - * Registry and factory for Customer.io React Native SDK modules and view managers. - * Provides centralized module creation for both old and new architecture. - * - * This object serves as the single source of truth for all TurboModule registrations - * and their corresponding factory methods. - */ -internal object CustomerIOReactNativePackageImpl { - val turboModuleNames: List - get() = listOf( - NativeCustomerIOLoggingModuleImpl.NAME, - NativeCustomerIOModuleImpl.NAME, - NativeMessagingInAppModuleImpl.NAME, - NativeMessagingPushModuleImpl.NAME, - ) - - @Suppress("UNCHECKED_CAST") - fun createNativeModule( - reactContext: ReactApplicationContext, - name: String - ): M? = when (name) { - NativeCustomerIOLoggingModuleImpl.NAME -> NativeCustomerIOLoggingModule(reactContext) - NativeCustomerIOModuleImpl.NAME -> NativeCustomerIOModule(reactContext = reactContext) - NativeMessagingInAppModuleImpl.NAME -> NativeMessagingInAppModule(reactContext) - NativeMessagingPushModuleImpl.NAME -> NativeMessagingPushModule(reactContext) - else -> assertNotNull(value = null) { "Unknown module name: $name" } - } as? M - - /** - * Creates the list of view managers for the Customer.io React Native SDK. - */ - fun createViewManagers(reactContext: ReactApplicationContext): List> { - return listOf(InlineInAppMessageViewManager()) - } -} diff --git a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt similarity index 78% rename from android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt rename to android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt index 1282c2d2..4e1cba09 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModuleImpl.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/NativeCustomerIOModule.kt @@ -8,8 +8,8 @@ import io.customer.datapipelines.config.ScreenView import io.customer.reactnative.sdk.constant.Keys import io.customer.reactnative.sdk.extension.getTypedValue import io.customer.reactnative.sdk.extension.toMap -import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModuleImpl -import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModuleImpl +import io.customer.reactnative.sdk.messaginginapp.NativeMessagingInAppModule +import io.customer.reactnative.sdk.messagingpush.NativeMessagingPushModule import io.customer.reactnative.sdk.util.assertNotNull import io.customer.sdk.CustomerIO import io.customer.sdk.CustomerIOBuilder @@ -22,13 +22,12 @@ import io.customer.sdk.events.TrackMetric import io.customer.sdk.events.serializedName /** - * Shared implementation logic for Customer.io Native SDK communication in React Native. - * Contains actual business logic used by both old and new architecture [NativeCustomerIOModule] classes. - * Handles SDK initialization, user identification, event tracking, and device management. + * React Native module implementation for Customer.io Native SDK + * using TurboModules with new architecture. */ -internal object NativeCustomerIOModuleImpl { - const val NAME = "NativeCustomerIO" - +class NativeCustomerIOModule( + private val reactContext: ReactApplicationContext, +) : NativeCustomerIOSpec(reactContext) { private val logger: Logger get() = SDKComponent.logger @@ -45,11 +44,8 @@ internal object NativeCustomerIOModuleImpl { logger.error("CustomerIO SDK is not initialized. Please call initialize() first.") } - fun initialize( - reactContext: ReactApplicationContext, - sdkConfig: ReadableMap?, - promise: Promise? - ) { + + override fun initialize(config: ReadableMap?, args: ReadableMap?, promise: Promise?) { // Skip initialization if already initialized if (getSDKInstanceOrNull() != null) { logger.info("CustomerIO SDK is already initialized. Skipping initialization.") @@ -58,7 +54,7 @@ internal object NativeCustomerIOModuleImpl { } try { - val packageConfig = sdkConfig.toMap() + val packageConfig = config.toMap() val cdpApiKey = packageConfig.getTypedValue( Keys.Config.CDP_API_KEY ) ?: throw IllegalArgumentException("CDP API Key is required to initialize Customer.io") @@ -90,14 +86,14 @@ internal object NativeCustomerIOModuleImpl { // Configure push messaging module based on config provided by customer app packageConfig.getTypedValue>(key = "push").let { pushConfig -> - NativeMessagingPushModuleImpl.addNativeModuleFromConfig( + NativeMessagingPushModule.addNativeModuleFromConfig( builder = this, config = pushConfig ?: emptyMap() ) } // Configure in-app messaging module based on config provided by customer app packageConfig.getTypedValue>(key = "inApp")?.let { inAppConfig -> - NativeMessagingInAppModuleImpl.addNativeModuleFromConfig( + NativeMessagingInAppModule.addNativeModuleFromConfig( builder = this, config = inAppConfig, region = region @@ -113,11 +109,7 @@ internal object NativeCustomerIOModuleImpl { } } - fun clearIdentify() { - requireSDKInstance()?.clearIdentify() - } - - fun identify(params: ReadableMap?) { + override fun identify(params: ReadableMap?) { val userId = params?.getString("userId") val traits = params?.getMap("traits") @@ -129,51 +121,55 @@ internal object NativeCustomerIOModuleImpl { userId?.let { requireSDKInstance()?.identify(userId, traits.toMap()) } ?: run { - requireSDKInstance()?.profileAttributes = traits.toMap() + requireSDKInstance()?.setProfileAttributes(traits.toMap()) } } - fun track(name: String?, properties: ReadableMap?) { + override fun clearIdentify() { + requireSDKInstance()?.clearIdentify() + } + + override fun track(name: String?, properties: ReadableMap?) { val eventName = assertNotNull(name) ?: return requireSDKInstance()?.track(eventName, properties.toMap()) } - fun setDeviceAttributes(attributes: ReadableMap?) { - requireSDKInstance()?.deviceAttributes = attributes.toMap() - } + override fun screen(title: String?, properties: ReadableMap?) { + val screenTitle = assertNotNull(title) ?: return - fun setProfileAttributes(attributes: ReadableMap?) { - requireSDKInstance()?.profileAttributes = attributes.toMap() + requireSDKInstance()?.screen(screenTitle, properties.toMap()) } - fun screen(title: String?, properties: ReadableMap?) { - val screenTitle = assertNotNull(title) ?: return + override fun setProfileAttributes(attributes: ReadableMap?) { + requireSDKInstance()?.setProfileAttributes(attributes.toMap()) + } - requireSDKInstance()?.screen(screenTitle, properties.toMap()) + override fun setDeviceAttributes(attributes: ReadableMap?) { + requireSDKInstance()?.setDeviceAttributes(attributes.toMap()) } - fun registerDeviceToken(token: String?) { + override fun registerDeviceToken(token: String?) { val deviceToken = assertNotNull(token) ?: return requireSDKInstance()?.registerDeviceToken(deviceToken) } - fun trackMetric(deliveryId: String?, deviceToken: String?, eventName: String?) { + override fun trackMetric(deliveryID: String?, deviceToken: String?, event: String?) { try { - if (deliveryId == null || deviceToken == null || eventName == null) { + if (deliveryID == null || deviceToken == null || event == null) { throw IllegalArgumentException("Missing required parameters") } - val event = Metric.values().find { - it.serializedName.equals(eventName, true) + val metric = Metric.values().find { + it.serializedName.equals(event, true) } ?: throw IllegalArgumentException("Invalid metric event name") requireSDKInstance()?.trackMetric( event = TrackMetric.Push( - deliveryId = deliveryId, + deliveryId = deliveryID, deviceToken = deviceToken, - metric = event + metric = metric ) ) } catch (e: Exception) { @@ -181,7 +177,11 @@ internal object NativeCustomerIOModuleImpl { } } - fun deleteDeviceToken() { + override fun deleteDeviceToken() { requireSDKInstance()?.deleteDeviceToken() } + + companion object { + internal const val NAME = "NativeCustomerIO" + } } diff --git a/android/src/newarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt b/android/src/main/java/io/customer/reactnative/sdk/extension/ViewExtensions.kt similarity index 100% rename from android/src/newarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt rename to android/src/main/java/io/customer/reactnative/sdk/extension/ViewExtensions.kt diff --git a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt new file mode 100644 index 00000000..a2b4fe0d --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt @@ -0,0 +1,90 @@ +package io.customer.reactnative.sdk.logging + +import android.util.Log +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import io.customer.reactnative.sdk.NativeCustomerIOLoggingSpec +import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.util.CioLogLevel + +/** + * React Native module implementation for Customer.io Logging Native SDK + * using TurboModules with new architecture. + */ +class NativeCustomerIOLoggingModule( + reactContext: ReactApplicationContext, +) : NativeCustomerIOLoggingSpec(reactContext) { + override fun getName(): String = NAME + + // Log event emitter function to send events to React Native layer + private var logEventEmitter: ((ReadableMap) -> Unit)? = null + + /** + * Executes the given block and logs any uncaught exceptions using Android logger to protect + * against unexpected crashes and failures. + */ + private fun runWithTryCatch(action: () -> Unit) { + try { + action() + } catch (ex: Exception) { + // Use Android logger to avoid cyclic calls from internal SDK logging + Log.e("[CIO]", "Error in NativeCustomerIOLoggingModule: ${ex.message}", ex) + } + } + + override fun initialize() { + runWithTryCatch { + super.initialize() + setLogEventEmitter { data -> + emitOnCioLogEvent(data) + } + } + } + + override fun invalidate() { + runWithTryCatch { + cleanupLogEventEmitter() + super.invalidate() + } + } + + private fun cleanupLogEventEmitter() { + runWithTryCatch { + // Clear log dispatcher to prevent memory leaks and further events + SDKComponent.logger.setLogDispatcher(null) + this.logEventEmitter = null + } + } + + // Sets the event emitter function used to send log events to React Native. + private fun setLogEventEmitter(emitter: ((ReadableMap) -> Unit)?) { + if (emitter == null) { + // Clear log dispatcher if emitter is null + cleanupLogEventEmitter() + return + } + + SDKComponent.logger.setLogDispatcher { level, message -> + emitLogEvent(level, message) + } + this.logEventEmitter = emitter + } + + // Converts native SDK log events to React Native compatible format and emits them. + private fun emitLogEvent(level: CioLogLevel, message: String) { + // Defensive check: only emit if emitter is available + val emitter = logEventEmitter ?: return + + val data = buildMap { + put("logLevel", level.name.lowercase()) + put("message", message) + } + + emitter.invoke(Arguments.makeNativeMap(data)) + } + + companion object { + internal const val NAME = "NativeCustomerIOLogging" + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt deleted file mode 100644 index bc275be8..00000000 --- a/android/src/main/java/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModuleImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.ReadableMap -import io.customer.sdk.core.di.SDKComponent -import io.customer.sdk.core.util.CioLogLevel - -/** - * Shared implementation for Customer.io Logging Native SDK module. - * Handles log event dispatching from the native SDK to React Native layer. - * Contains the actual business logic used by both new and old architecture modules. - */ -object NativeCustomerIOLoggingModuleImpl { - const val NAME = "NativeCustomerIOLogging" - - // Log event emitter function to send events to React Native layer - private var logEventEmitter: ((ReadableMap) -> Unit)? = null - - // Sets the event emitter function used to send log events to React Native. - internal fun setLogEventEmitter(emitter: ((ReadableMap) -> Unit)?) { - // Set up log dispatcher only when first emitter is set - if (emitter != null && logEventEmitter == null) { - SDKComponent.logger.setLogDispatcher { level, message -> - emitLogEvent(level, message) - } - } - - this.logEventEmitter = emitter - } - - // Clears the event emitter, should be called during module cleanup - internal fun invalidate() { - // Clear log dispatcher to prevent memory leaks and further events - SDKComponent.logger.setLogDispatcher(null) - this.logEventEmitter = null - } - - // Converts native SDK log events to React Native compatible format and emits them. - private fun emitLogEvent(level: CioLogLevel, message: String) { - // Defensive check: only emit if emitter is available - val emitter = logEventEmitter ?: return - - val data = buildMap { - put("logLevel", level.name.lowercase()) - put("message", message) - } - - emitter.invoke(Arguments.makeNativeMap(data)) - } -} diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt similarity index 59% rename from android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt rename to android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt index d45511b3..c976e0e9 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/BaseInlineInAppMessageViewManager.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt @@ -1,8 +1,12 @@ package io.customer.reactnative.sdk.messaginginapp +import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.InlineMessageNativeManagerDelegate +import com.facebook.react.viewmanagers.InlineMessageNativeManagerInterface import io.customer.messaginginapp.ui.bridge.WrapperPlatformDelegate /** @@ -11,12 +15,17 @@ import io.customer.messaginginapp.ui.bridge.WrapperPlatformDelegate * Provides common functionality for both old and new React Native architecture * implementations, including view creation, event handling, and property management. */ -abstract class BaseInlineInAppMessageViewManager : +@ReactModule(name = InlineInAppMessageViewManager.NAME) +class InlineInAppMessageViewManager : + InlineMessageNativeManagerInterface, SimpleViewManager() { + private val delegate = InlineMessageNativeManagerDelegate(this) + override fun getName() = NAME + override fun getDelegate(): ViewManagerDelegate = delegate - override fun createViewInstance(context: ThemedReactContext): ReactInlineInAppMessageView { - return ReactInlineInAppMessageView(context) + override fun createViewInstance(reactContext: ThemedReactContext): ReactInlineInAppMessageView { + return ReactInlineInAppMessageView(reactContext) } /** @@ -25,7 +34,7 @@ abstract class BaseInlineInAppMessageViewManager : * - onSizeChange: Triggered when the size of the inline message changes. * - onStateChange: Triggered when the state of the inline message changes. */ - override fun getExportedCustomDirectEventTypeConstants(): MutableMap? { + override fun getExportedCustomDirectEventTypeConstants(): Map { val customEvents = super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf() val registerCustomEvent = { eventName: String -> customEvents.put(eventName, mapOf("registrationName" to eventName)) @@ -37,11 +46,11 @@ abstract class BaseInlineInAppMessageViewManager : } @ReactProp(name = "elementId") - fun setElementId(view: ReactInlineInAppMessageView, elementId: String?) { - view.elementId = elementId + override fun setElementId(view: ReactInlineInAppMessageView?, value: String?) { + view?.elementId = value } companion object { - const val NAME = "InlineMessageNative" + internal const val NAME = "InlineMessageNative" } } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt new file mode 100644 index 00000000..fbd2cc85 --- /dev/null +++ b/android/src/main/java/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt @@ -0,0 +1,73 @@ +package io.customer.reactnative.sdk.messaginginapp + +import com.facebook.react.bridge.ReactApplicationContext +import io.customer.messaginginapp.MessagingInAppModuleConfig +import io.customer.messaginginapp.ModuleMessagingInApp +import io.customer.messaginginapp.di.inAppMessaging +import io.customer.reactnative.sdk.NativeCustomerIOMessagingInAppSpec +import io.customer.reactnative.sdk.constant.Keys +import io.customer.reactnative.sdk.extension.getTypedValue +import io.customer.sdk.CustomerIO +import io.customer.sdk.CustomerIOBuilder +import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.data.model.Region + +/** + * React Native module implementation for Customer.io In-App Messaging Native SDK + * using TurboModules with new architecture. + */ +class NativeMessagingInAppModule( + reactContext: ReactApplicationContext, +) : NativeCustomerIOMessagingInAppSpec(reactContext) { + private val inAppMessagingModule: ModuleMessagingInApp? + get() = kotlin.runCatching { CustomerIO.instance().inAppMessaging() }.getOrNull() + + private val inAppEventListener = ReactInAppEventListener.instance + + override fun initialize() { + super.initialize() + inAppEventListener.setEventEmitter { data -> + emitOnInAppEventReceived(data) + } + } + + override fun invalidate() { + inAppEventListener.clearEventEmitter() + super.invalidate() + } + + override fun dismissMessage() { + inAppMessagingModule?.dismissMessage() + } + + companion object { + internal const val NAME = "NativeCustomerIOMessagingInApp" + + /** + * Adds InAppMessaging module to native Android SDK based on configuration provided by customer + * app. + * + * @param builder CustomerIOBuilder instance to add InAppMessaging module + * @param config Configuration provided by customer app for InAppMessaging module + * @param region Region to be used for InAppMessaging module + */ + internal fun addNativeModuleFromConfig( + builder: CustomerIOBuilder, + config: Map, + region: Region + ) { + val siteId = config.getTypedValue(Keys.Config.SITE_ID) + if (siteId.isNullOrBlank()) { + SDKComponent.logger.error("Site ID is required to initialize InAppMessaging module") + return + } + + val module = ModuleMessagingInApp( + MessagingInAppModuleConfig.Builder(siteId = siteId, region = region).apply { + setEventListener(eventListener = ReactInAppEventListener.instance) + }.build(), + ) + builder.addCustomerIOModule(module) + } + } +} diff --git a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt similarity index 66% rename from android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt rename to android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt index bc8398c8..9633511c 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModuleImpl.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt @@ -8,6 +8,7 @@ import android.os.Build import androidx.core.content.ContextCompat import com.facebook.react.bridge.ActivityEventListener import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReadableMap import com.facebook.react.modules.core.PermissionAwareActivity @@ -18,9 +19,11 @@ import io.customer.messagingpush.ModuleMessagingPushFCM import io.customer.messagingpush.config.PushClickBehavior import io.customer.messagingpush.di.pushModuleConfig import io.customer.messagingpush.di.pushTrackingUtil +import io.customer.reactnative.sdk.NativeCustomerIOMessagingPushSpec import io.customer.reactnative.sdk.constant.Keys import io.customer.reactnative.sdk.extension.getTypedValue import io.customer.reactnative.sdk.extension.takeIfNotBlank +import io.customer.reactnative.sdk.util.unsupportedOnAndroid import io.customer.sdk.CustomerIO import io.customer.sdk.CustomerIOBuilder import io.customer.sdk.core.di.SDKComponent @@ -28,23 +31,14 @@ import io.customer.sdk.core.util.Logger import java.util.UUID /** - * Shared implementation for Customer.io Push Messaging Native SDK module. - * Contains the actual business logic used by both new and old architecture modules. + * React Native module implementation for Customer.io Push Messaging Native SDK using + * TurboModules with new architecture. */ -object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener { - const val NAME = "NativeCustomerIOMessagingPush" - - /** - * Copying value os [Manifest.permission.POST_NOTIFICATIONS] as constant so we don't have to - * force newer compile sdk versions - */ - private const val POST_NOTIFICATIONS_PERMISSION_NAME = "android.permission.POST_NOTIFICATIONS" - private const val BUILD_VERSION_CODE_TIRAMISU = 33 - private const val POST_NOTIFICATIONS_PERMISSION_REQUEST = 24676 - - private val logger: Logger = SDKComponent.logger - private val pushModuleConfig: MessagingPushModuleConfig - get() = SDKComponent.pushModuleConfig +class NativeMessagingPushModule( + private val reactContext: ReactApplicationContext, +) : NativeCustomerIOMessagingPushSpec(reactContext), PermissionListener, ActivityEventListener { + private val logger: Logger + get() = SDKComponent.logger /** * Temporarily holds reference for notification request as the request is dependent on Android @@ -52,40 +46,17 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener */ private var notificationRequestPromise: Promise? = null - /** - * Adds push messaging module to native Android SDK based on the configuration provided by - * customer app. - * - * @param builder instance of CustomerIOBuilder to add push messaging module. - * @param config configuration provided by customer app for push messaging module. - */ - fun addNativeModuleFromConfig( - builder: CustomerIOBuilder, - config: Map - ) { - val androidConfig = config.getTypedValue>(key = "android") ?: emptyMap() - // Prefer `android` object for push configurations as it's more specific to Android - // For common push configurations, use `config` object instead of `android` - - // Default push click behavior is to prevent restart of activity in React Native apps - val pushClickBehavior = androidConfig.getTypedValue(Keys.Config.PUSH_CLICK_BEHAVIOR) - ?.takeIfNotBlank() - ?.let { value -> - runCatching { enumValueOf(value) }.getOrNull() - } ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART + override fun initialize() { + super.initialize() + reactContext.addActivityEventListener(this) + } - val module = ModuleMessagingPushFCM( - moduleConfig = MessagingPushModuleConfig.Builder().apply { - setPushClickBehavior(pushClickBehavior = pushClickBehavior) - }.build(), - ) - builder.addCustomerIOModule(module) + override fun invalidate() { + reactContext.removeActivityEventListener(this) + super.invalidate() } - fun getPushPermissionStatus( - reactContext: ReactContext, - promise: Promise?, - ) { + override fun getPushPermissionStatus(promise: Promise?) { val result = checkPushPermissionStatus(reactContext).toReactNativeResult promise?.resolve(result) } @@ -96,15 +67,11 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener * For newer versions, the permission is requested and the promise is resolved after the request * has been completed. * - * @param pushConfigurationOptions configurations options for push notifications, required for - * iOS only, unused on Android. + * @param options configurations options for push notifications, required for iOS only, + * unused on Android. * @param promise to resolve and return the results. */ - fun showPromptForPushNotifications( - reactContext: ReactContext, - pushConfigurationOptions: ReadableMap?, - promise: Promise?, - ) { + override fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise?) { // Skip requesting permissions when already granted if (checkPushPermissionStatus(reactContext) == PermissionStatus.Granted) { promise?.resolve(PermissionStatus.Granted.toReactNativeResult) @@ -141,8 +108,7 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener * @param message push payload received from FCM. * @param handleNotificationTrigger indicating if the local notification should be triggered. */ - fun onMessageReceived( - reactContext: ReactContext, + override fun onMessageReceived( message: ReadableMap?, handleNotificationTrigger: Boolean, promise: Promise?, @@ -168,12 +134,20 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } } + override fun trackNotificationResponseReceived(payload: ReadableMap?) { + unsupportedOnAndroid(methodName = "trackNotificationResponseReceived") + } + + override fun trackNotificationReceived(payload: ReadableMap?) { + unsupportedOnAndroid(methodName = "trackNotificationReceived") + } + /** * Get the registered device token for the app. * @returns Promise with device token as a string, or error if no token is * registered or the method fails to fetch token. */ - fun getRegisteredDeviceToken(promise: Promise?) { + override fun getRegisteredDeviceToken(promise: Promise?) { try { // Get the device token from SDK val deviceToken: String? = CustomerIO.instance().registeredDeviceToken @@ -195,13 +169,14 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener /** * Checks current permission of push notification permission */ - private fun checkPushPermissionStatus(reactContext: ReactContext): PermissionStatus = + private fun checkPushPermissionStatus(reactContext: ReactContext): PermissionStatus { // Skip requesting permissions for older versions where not required - if (Build.VERSION.SDK_INT < BUILD_VERSION_CODE_TIRAMISU || ContextCompat.checkSelfPermission( + return if (Build.VERSION.SDK_INT < BUILD_VERSION_CODE_TIRAMISU || ContextCompat.checkSelfPermission( reactContext, POST_NOTIFICATIONS_PERMISSION_NAME, ) == PackageManager.PERMISSION_GRANTED ) PermissionStatus.Granted else PermissionStatus.Denied + } /** * Resolves and clears promise with the provided permission status @@ -212,7 +187,9 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray, + requestCode: Int, + permissions: Array, + grantResults: IntArray, ): Boolean = when (requestCode) { POST_NOTIFICATIONS_PERMISSION_REQUEST -> { // If request is cancelled, the result arrays are empty. @@ -256,9 +233,51 @@ object NativeMessagingPushModuleImpl : PermissionListener, ActivityEventListener } } - /** - * Maps native class to react native supported type so the result can be passed on to JS/TS classes. - */ - private val PermissionStatus.toReactNativeResult: Any - get() = this.name.uppercase() + companion object { + const val NAME = "NativeCustomerIOMessagingPush" + + /** + * Copying value os [Manifest.permission.POST_NOTIFICATIONS] as constant so we don't have to + * force newer compile sdk versions + */ + private const val POST_NOTIFICATIONS_PERMISSION_NAME = + "android.permission.POST_NOTIFICATIONS" + private const val BUILD_VERSION_CODE_TIRAMISU = 33 + private const val POST_NOTIFICATIONS_PERMISSION_REQUEST = 24676 + + private val pushModuleConfig: MessagingPushModuleConfig + get() = SDKComponent.pushModuleConfig + + /** + * Adds push messaging module to native Android SDK based on the configuration provided by + * customer app. + * + * @param builder instance of CustomerIOBuilder to add push messaging module. + * @param config configuration provided by customer app for push messaging module. + */ + internal fun addNativeModuleFromConfig( + builder: CustomerIOBuilder, + config: Map + ) { + val androidConfig = + config.getTypedValue>(key = "android") ?: emptyMap() + // Prefer `android` object for push configurations as it's more specific to Android + // For common push configurations, use `config` object instead of `android` + + // Default push click behavior is to prevent restart of activity in React Native apps + val pushClickBehavior = + androidConfig.getTypedValue(Keys.Config.PUSH_CLICK_BEHAVIOR) + ?.takeIfNotBlank() + ?.let { value -> + runCatching { enumValueOf(value) }.getOrNull() + } ?: PushClickBehavior.ACTIVITY_PREVENT_RESTART + + val module = ModuleMessagingPushFCM( + moduleConfig = MessagingPushModuleConfig.Builder().apply { + setPushClickBehavior(pushClickBehavior = pushClickBehavior) + }.build(), + ) + builder.addCustomerIOModule(module) + } + } } diff --git a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt index ba886ae7..4bbae8d9 100644 --- a/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt +++ b/android/src/main/java/io/customer/reactnative/sdk/messagingpush/PushMessagingExtensions.kt @@ -50,3 +50,9 @@ internal fun ReadableMap.toFCMRemoteMessage(destination: String): RemoteMessage build() } } + +/** + * Maps native class to react native supported type so the result can be passed on to JS/TS classes. + */ +internal val PermissionStatus.toReactNativeResult: Any + get() = this.name.uppercase() diff --git a/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt deleted file mode 100644 index 89bf2a09..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.BaseReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.model.ReactModuleInfo -import com.facebook.react.module.model.ReactModuleInfoProvider -import com.facebook.react.uimanager.ViewManager -import io.customer.reactnative.sdk.messaginginapp.BaseInlineInAppMessageViewManager -import io.customer.reactnative.sdk.messaginginapp.InlineInAppMessageViewManager - -class CustomerIOReactNativePackage : BaseReactPackage() { - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return CustomerIOReactNativePackageImpl.createViewManagers(reactContext) - } - - override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - // Debugging reveals that this method is never called for ViewManagers. - // But since ReactNative docs recommend overriding it, we do so here for ViewManagers. - // See: https://reactnative.dev/docs/fabric-native-components-introduction?platforms=android#4-write-the-reactwebviewpackage - return when (name) { - BaseInlineInAppMessageViewManager.NAME -> InlineInAppMessageViewManager() - else -> CustomerIOReactNativePackageImpl.createNativeModule(reactContext, name) - } - } - - /** - * Creates a ReactModuleInfo for React module registration with the given configuration. - * Using positional arguments instead of named arguments as named args break on RN 0.76. - */ - private fun createReactModuleInfo( - name: String, - className: String = name, - canOverrideExistingModule: Boolean = false, - needsEagerInit: Boolean = false, - isCxxModule: Boolean = false, - isTurboModule: Boolean = true, - ) = ReactModuleInfo( - name, - className, - canOverrideExistingModule, - needsEagerInit, - isCxxModule, - isTurboModule, - ) - - override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { - buildMap { - // Register TurboModules - CustomerIOReactNativePackageImpl.turboModuleNames.forEach { moduleName -> - put(moduleName, createReactModuleInfo(name = moduleName)) - } - - // Register ViewManagers - val viewManagerName = BaseInlineInAppMessageViewManager.NAME - val viewManagerInfo = createReactModuleInfo(name = viewManagerName) - put(viewManagerName, viewManagerInfo) - } - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt b/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt deleted file mode 100644 index 4503345a..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap - -/** - * React Native module implementation for Customer.io Native SDK using using - * Turbo Modules with new architecture. - */ -class NativeCustomerIOModule( - private val reactContext: ReactApplicationContext, -) : NativeCustomerIOSpec(reactContext) { - - override fun initialize(config: ReadableMap?, args: ReadableMap?, promise: Promise?) { - NativeCustomerIOModuleImpl.initialize( - reactContext = reactContext, - sdkConfig = config, - promise = promise, - ) - } - - override fun identify(params: ReadableMap?) { - NativeCustomerIOModuleImpl.identify(params) - } - - override fun clearIdentify() { - NativeCustomerIOModuleImpl.clearIdentify() - } - - override fun track(name: String?, properties: ReadableMap?) { - NativeCustomerIOModuleImpl.track(name, properties) - } - - override fun screen(title: String?, properties: ReadableMap?) { - NativeCustomerIOModuleImpl.screen(title, properties) - } - - override fun setProfileAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setProfileAttributes(attributes) - } - - override fun setDeviceAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setDeviceAttributes(attributes) - } - - override fun registerDeviceToken(token: String?) { - NativeCustomerIOModuleImpl.registerDeviceToken(token) - } - - override fun trackMetric(deliveryID: String?, deviceToken: String?, event: String?) { - NativeCustomerIOModuleImpl.trackMetric(deliveryID, deviceToken, event) - } - - override fun deleteDeviceToken() { - NativeCustomerIOModuleImpl.deleteDeviceToken() - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt deleted file mode 100644 index 49cc423f..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import android.util.Log -import com.facebook.react.bridge.ReactApplicationContext -import io.customer.reactnative.sdk.NativeCustomerIOLoggingSpec - -/** - * React Native module implementation for Customer.io Logging Native SDK - * using TurboModules with new architecture. - */ -class NativeCustomerIOLoggingModule( - reactContext: ReactApplicationContext, -) : NativeCustomerIOLoggingSpec(reactContext) { - override fun getName(): String = NativeCustomerIOLoggingModuleImpl.NAME - - /** - * Executes the given block and logs any uncaught exceptions using Android logger to protect - * against unexpected crashes and failures. - */ - private fun runWithTryCatch(action: () -> Unit) { - try { - action() - } catch (ex: Exception) { - // Use Android logger to avoid cyclic calls from internal SDK logging - Log.e("[CIO]", "Error in NativeCustomerIOLoggingModule: ${ex.message}", ex) - } - } - - override fun initialize() { - runWithTryCatch { - super.initialize() - NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> - emitOnCioLogEvent(data) - } - } - } - - override fun invalidate() { - runWithTryCatch { - NativeCustomerIOLoggingModuleImpl.invalidate() - } - runWithTryCatch { - super.invalidate() - } - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt deleted file mode 100644 index 6b835b24..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.viewmanagers.InlineMessageNativeManagerDelegate -import com.facebook.react.viewmanagers.InlineMessageNativeManagerInterface - -@ReactModule(name = BaseInlineInAppMessageViewManager.NAME) -class InlineInAppMessageViewManager : BaseInlineInAppMessageViewManager(), - InlineMessageNativeManagerInterface { - private val delegate = InlineMessageNativeManagerDelegate(this) - - override fun getDelegate(): ViewManagerDelegate = delegate -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt deleted file mode 100644 index 3024167e..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import com.facebook.react.bridge.ReactApplicationContext -import io.customer.reactnative.sdk.NativeCustomerIOMessagingInAppSpec - -/** - * React Native module implementation for Customer.io In-App Messaging Native SDK - * using TurboModules with new architecture. - */ -class NativeMessagingInAppModule( - reactContext: ReactApplicationContext, -) : NativeCustomerIOMessagingInAppSpec(reactContext) { - private val inAppEventListener: ReactInAppEventListener - get() = NativeMessagingInAppModuleImpl.inAppEventListener - - override fun initialize() { - super.initialize() - inAppEventListener.setEventEmitter { data -> - emitOnInAppEventReceived(data) - } - } - - override fun invalidate() { - inAppEventListener.clearEventEmitter() - super.invalidate() - } - - override fun dismissMessage() { - NativeMessagingInAppModuleImpl.dismissMessage() - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt b/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt deleted file mode 100644 index e230cdda..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +++ /dev/null @@ -1,66 +0,0 @@ -package io.customer.reactnative.sdk.messagingpush - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap -import io.customer.reactnative.sdk.NativeCustomerIOMessagingPushSpec -import io.customer.reactnative.sdk.util.unsupportedOnAndroid - -/** - * React Native module implementation for Customer.io Push Messaging Native SDK using - * TurboModules with new architecture. - */ -class NativeMessagingPushModule( - private val reactContext: ReactApplicationContext, -) : NativeCustomerIOMessagingPushSpec(reactContext) { - - override fun initialize() { - super.initialize() - reactContext.addActivityEventListener(NativeMessagingPushModuleImpl) - } - - override fun invalidate() { - reactContext.removeActivityEventListener(NativeMessagingPushModuleImpl) - super.invalidate() - } - - override fun onMessageReceived( - message: ReadableMap?, - handleNotificationTrigger: Boolean, - promise: Promise?, - ) { - NativeMessagingPushModuleImpl.onMessageReceived( - reactContext = reactContext, - message = message, - handleNotificationTrigger = handleNotificationTrigger, - promise = promise, - ) - } - - override fun trackNotificationResponseReceived(payload: ReadableMap?) { - unsupportedOnAndroid(methodName = "trackNotificationResponseReceived") - } - - override fun trackNotificationReceived(payload: ReadableMap?) { - unsupportedOnAndroid(methodName = "trackNotificationReceived") - } - - override fun getRegisteredDeviceToken(promise: Promise?) { - NativeMessagingPushModuleImpl.getRegisteredDeviceToken(promise) - } - - override fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise?) { - NativeMessagingPushModuleImpl.showPromptForPushNotifications( - reactContext = reactContext, - pushConfigurationOptions = options, - promise = promise, - ) - } - - override fun getPushPermissionStatus(promise: Promise?) { - NativeMessagingPushModuleImpl.getPushPermissionStatus( - reactContext = reactContext, - promise = promise, - ) - } -} diff --git a/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt b/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt deleted file mode 100644 index a48dfe3e..00000000 --- a/android/src/newarch/io/customer/reactnative/sdk/util/ArchUtil.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.customer.reactnative.sdk.util - -import io.customer.reactnative.sdk.BuildConfig -import io.customer.sdk.core.di.SDKComponent - -/** - * Marks a method that's only implemented for legacy architecture support. - * Throws in debug; logs an error in release builds. - */ -fun onlyForLegacyArch(methodName: String) { - val message = "'$methodName' is not required in the New Architecture and should not be called." - - if (BuildConfig.DEBUG) { - error(message) - } else { - SDKComponent.logger.error(message) - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt b/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt deleted file mode 100644 index 8e43a539..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.ReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager - -class CustomerIOReactNativePackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return CustomerIOReactNativePackageImpl.turboModuleNames.mapNotNull { name -> - CustomerIOReactNativePackageImpl.createNativeModule( - reactContext = reactContext, - name = name, - ) - } - } - - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return CustomerIOReactNativePackageImpl.createViewManagers(reactContext) - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt deleted file mode 100644 index 04b91daf..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/NativeCustomerIOModule.kt +++ /dev/null @@ -1,74 +0,0 @@ -package io.customer.reactnative.sdk - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.bridge.ReadableMap - -/** - * React Native module implementation for Customer.io Native SDK using using old architecture. - */ -class NativeCustomerIOModule( - reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeCustomerIOModuleImpl.NAME - - @ReactMethod - fun initialize( - configJson: ReadableMap, - @Suppress("UNUSED_PARAMETER") sdkArgs: ReadableMap?, - promise: Promise?, - ) { - NativeCustomerIOModuleImpl.initialize( - reactContext = reactApplicationContext, - sdkConfig = configJson, - promise = promise, - ) - } - - @ReactMethod - fun clearIdentify() { - NativeCustomerIOModuleImpl.clearIdentify() - } - - @ReactMethod - fun identify(params: ReadableMap?) { - NativeCustomerIOModuleImpl.identify(params = params) - } - - @ReactMethod - fun track(name: String, attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.track(name, attributes) - } - - @ReactMethod - fun setDeviceAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setDeviceAttributes(attributes) - } - - @ReactMethod - fun setProfileAttributes(attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.setProfileAttributes(attributes) - } - - @ReactMethod - fun screen(name: String, attributes: ReadableMap?) { - NativeCustomerIOModuleImpl.screen(name, attributes) - } - - @ReactMethod - fun registerDeviceToken(token: String) { - NativeCustomerIOModuleImpl.registerDeviceToken(token) - } - - @ReactMethod - fun trackMetric(deliveryID: String, deviceToken: String, event: String) { - NativeCustomerIOModuleImpl.trackMetric(deliveryID, deviceToken, event) - } - - @ReactMethod - fun deleteDeviceToken() { - NativeCustomerIOModuleImpl.deleteDeviceToken() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt b/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt deleted file mode 100644 index c0aed903..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/extension/ViewExtensions.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.customer.reactnative.sdk.extension - -import android.view.View -import com.facebook.react.bridge.WritableMap -import com.facebook.react.uimanager.events.RCTEventEmitter - -/** - * Extension function to send UI events from native Android views to React JS. - * Old Architecture (Paper) compatible implementation using RCTEventEmitter. - * - * @param eventName The name of the event to be sent to React JS - * @param payload Optional data payload to include with the event - */ -internal fun View.sendUIEventToReactJS( - eventName: String, - payload: WritableMap? = null, -) { - reactContext - .getJSModule(RCTEventEmitter::class.java) - .receiveEvent(id, eventName, payload) -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt deleted file mode 100644 index ea5906af..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/logging/NativeCustomerIOLoggingModule.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.customer.reactnative.sdk.logging - -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.modules.core.DeviceEventManagerModule - -/** - * React Native module implementation for Customer.io Logging Native SDK - * using legacy architecture. - */ -class NativeCustomerIOLoggingModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeCustomerIOLoggingModuleImpl.NAME - - private var listenerCount = 0 - - override fun initialize() { - super.initialize() - NativeCustomerIOLoggingModuleImpl.setLogEventEmitter { data -> - if (listenerCount <= 0) return@setLogEventEmitter - - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit("CioLogEvent", data) - } - } - - override fun invalidate() { - NativeCustomerIOLoggingModuleImpl.invalidate() - super.invalidate() - } - - @ReactMethod - fun addListener(eventName: String?) { - listenerCount++ - } - - @ReactMethod - fun removeListeners(count: Double) { - listenerCount -= count.toInt() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt b/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt deleted file mode 100644 index 382b83ba..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/InlineInAppMessageViewManager.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -class InlineInAppMessageViewManager : BaseInlineInAppMessageViewManager() diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt deleted file mode 100644 index 7c8b8dda..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.customer.reactnative.sdk.messaginginapp - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.modules.core.DeviceEventManagerModule - -/** - * React Native module implementation for Customer.io In-App Messaging Native - * SDK using old architecture. - */ -class NativeMessagingInAppModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeMessagingInAppModuleImpl.NAME - - private var listenerCount = 0 - private val inAppEventListener: ReactInAppEventListener - get() = NativeMessagingInAppModuleImpl.inAppEventListener - - override fun initialize() { - super.initialize() - inAppEventListener.setEventEmitter { data -> - if (listenerCount <= 0) return@setEventEmitter - - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit("InAppEventListener", data) - } - } - - override fun invalidate() { - inAppEventListener.clearEventEmitter() - super.invalidate() - } - - @ReactMethod - fun dismissMessage() { - NativeMessagingInAppModuleImpl.dismissMessage() - } - - @ReactMethod - fun addListener(eventName: String?) { - listenerCount++ - } - - @ReactMethod - fun removeListeners(count: Double) { - listenerCount -= count.toInt() - } -} diff --git a/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt b/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt deleted file mode 100644 index 7e3db553..00000000 --- a/android/src/oldarch/io/customer/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.customer.reactnative.sdk.messagingpush - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.bridge.ReadableMap - -/** - * React Native module implementation for Customer.io Push Messaging Native SDK using old architecture. - */ -class NativeMessagingPushModule( - private val reactContext: ReactApplicationContext, -) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NativeMessagingPushModuleImpl.NAME - - override fun initialize() { - super.initialize() - reactContext.addActivityEventListener(NativeMessagingPushModuleImpl) - } - - override fun invalidate() { - reactContext.removeActivityEventListener(NativeMessagingPushModuleImpl) - super.invalidate() - } - - @ReactMethod - fun onMessageReceived( - message: ReadableMap?, - handleNotificationTrigger: Boolean, - promise: Promise, - ) { - NativeMessagingPushModuleImpl.onMessageReceived( - reactContext = reactContext, - message = message, - handleNotificationTrigger = handleNotificationTrigger, - promise = promise, - ) - } - - @ReactMethod - fun getRegisteredDeviceToken(promise: Promise) { - NativeMessagingPushModuleImpl.getRegisteredDeviceToken(promise) - } - - @ReactMethod - fun showPromptForPushNotifications(options: ReadableMap?, promise: Promise) { - NativeMessagingPushModuleImpl.showPromptForPushNotifications( - reactContext = reactContext, - pushConfigurationOptions = options, - promise = promise, - ) - } - - @ReactMethod - fun getPushPermissionStatus(promise: Promise) { - NativeMessagingPushModuleImpl.getPushPermissionStatus( - reactContext = reactContext, - promise = promise, - ) - } -} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index f1827141..24ef29be 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -47,8 +47,3 @@ edgeToEdgeEnabled=false # This is useful for testing new features or bug fixes before they are released. # Set to 'local' to use the local version of the SDK. # cioSDKVersionAndroid=local - -# Customer.io SDK allows overriding new architecture setting. -# This is useful as a workaround for apps where the new architecture has issues. -# Set to 'false' to force the SDK to use old architecture even when newArchEnabled=true. -# customerioNewArchEnabled=false