Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 1 addition & 30 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not entirely new file, but just merged version of new arch + common implementation of CustomerIOReactNativePackage

/**
* Creates the list of view managers for the Customer.io React Native SDK.
*/
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
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<NativeModule>(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<String> = listOf(
InlineInAppMessageViewManager.NAME,
NativeCustomerIOLoggingModule.NAME,
NativeCustomerIOModule.NAME,
NativeMessagingInAppModule.NAME,
NativeMessagingPushModule.NAME,
)
return ReactModuleInfoProvider {
// Register all ViewManagers and TurboModules
moduleNames.associateWith { moduleName ->
createReactModuleInfo(name = moduleName)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not entirely new file, but just merged version of new arch + common implementation of NativeCustomerIOModule

private val reactContext: ReactApplicationContext,
) : NativeCustomerIOSpec(reactContext) {
private val logger: Logger
get() = SDKComponent.logger

Expand All @@ -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.")
Expand All @@ -58,7 +54,7 @@ internal object NativeCustomerIOModuleImpl {
}

try {
val packageConfig = sdkConfig.toMap()
val packageConfig = config.toMap()
val cdpApiKey = packageConfig.getTypedValue<String>(
Keys.Config.CDP_API_KEY
) ?: throw IllegalArgumentException("CDP API Key is required to initialize Customer.io")
Expand Down Expand Up @@ -90,14 +86,14 @@ internal object NativeCustomerIOModuleImpl {

// Configure push messaging module based on config provided by customer app
packageConfig.getTypedValue<Map<String, Any>>(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<Map<String, Any>>(key = "inApp")?.let { inAppConfig ->
NativeMessagingInAppModuleImpl.addNativeModuleFromConfig(
NativeMessagingInAppModule.addNativeModuleFromConfig(
builder = this,
config = inAppConfig,
region = region
Expand All @@ -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")

Expand All @@ -129,59 +121,67 @@ 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) {
logger.error("Error tracking push metric: ${e.message}")
}
}

fun deleteDeviceToken() {
override fun deleteDeviceToken() {
requireSDKInstance()?.deleteDeviceToken()
}

companion object {
internal const val NAME = "NativeCustomerIO"
}
}
Loading
Loading