Conversation
Integrate native Android and iOS location modules into the React Native SDK. Adds TurboModule spec, native bridges, TypeScript API, config types, and dependency declarations for the location feature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Location is now opt-in: iOS uses a CocoaPods subspec with #if canImport guards, Android uses compileOnly with reflection-based class loading. Customers who don't need location no longer pull in the dependency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ional location iOS: Replace #if canImport(CioLocation) with #if CIO_LOCATION_ENABLED compiler flag set via podspec subspec's pod_target_xcconfig. This is deterministic regardless of CocoaPods linking mode. Android: Replace reflection-based class loading with BuildConfig flag controlled by customerio_location_enabled gradle property. Dependency is conditionally included as implementation or compileOnly based on the property. No reflection needed, no ProGuard concerns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix critical bug: NSClassFromString used wrong class name (@"NativeLocation" instead of @"NativeCustomerIOLocation"), causing iOS location to silently no-op even when enabled - Gate NativeLocationModule registration in getReactModuleInfoProvider on BuildConfig.CIO_LOCATION_ENABLED to avoid unnecessary module info - Consolidate duplicate cioLocationEnabled definition into ext property - Add NativeLocationSpec implements clause for compile-time type safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sample app builds 📱Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.
|
rootProject.hasProperty() returns true even for customerio_location_enabled=false. Use findProperty().toBoolean() to correctly respect the property's actual value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TurboModuleRegistry.getEnforcing() runs at import time and throws if the module isn't in the info provider. The module must always be registered; getModule() already returns null when location is disabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
android/src/main/java/io/customer/reactnative/sdk/CustomerIOReactNativePackage.kt
Show resolved
Hide resolved
| if (project.cioLocationEnabled) { | ||
| implementation "io.customer.android:location:$cioAndroidSDKVersion" | ||
| } else { | ||
| compileOnly "io.customer.android:location:$cioAndroidSDKVersion" |
There was a problem hiding this comment.
This is a nice addition but it probably won't work properly since the code still references location classes. Ideally to solve this, we would need to completely remove location class references when the build flag is disabled to fully eliminate these challenges. So I think this is a bigger problem to solve. Unless we really need this for location right now, we should probably address it in a separate PR.
There was a problem hiding this comment.
- Kotlin compiler eliminates dead code at compile time; When
BuildConfig.CIO_LOCATION_ENABLED = false, the compiler strips the entire if block from bytecode. I decompiled the AAR and confirmed:
NativeCustomerIOModulehas zero references to anyio.customer.location.*class in its constant pool.
The getModule() path compiles down to just aconst_null (return null). - Manifest permissions don't leak — Built the RN sample app with location disabled and checked the
merged manifest.ACCESS_COARSE_LOCATIONis completely absent.compileOnlymeans the location AAR's manifest is never fed to the manifest merger. - App runs without crashes — Installed and launched on an emulator with location disabled. No
NoClassDefFoundError or ClassNotFoundException.
The compileOnly + BuildConfig approach serves our exact use case: customers who don't enable
location don't get the permission in their app.
There was a problem hiding this comment.
Thanks for the detailed response. Can you check why the sample app build is failing during minifyReleaseWithR8 then?
There was a problem hiding this comment.
Seems like when R8 runs on a release build, it scans all classes and fails because the compileOnly location classes aren't on the runtime classpath. Adding rule would solve that.
|
|
||
| - (void)setLastKnownLocation:(double)latitude | ||
| longitude:(double)longitude { | ||
| if (!_swiftBridge) return; |
There was a problem hiding this comment.
There is a helper assertBridgeAvailable method for this in case you want to utilize it
There was a problem hiding this comment.
assertBridgeAvailable calls RCT_ASSERT_NOT_NIL which crashes when the bridge is nil. That's correct
for push/in-app since they're always required — if the bridge is missing, something is broken.
For location, the bridge being nil is expected when the customer hasn't installed the location
subspec. Using assertBridgeAvailable would crash every app that doesn't opt into location. The if
(!_swiftBridge) return; pattern is the right one here — it silently no-ops, which is the intended
behavior for an optional module.
There was a problem hiding this comment.
I'm okay with the current implementation. But just for clarity, RCT_ASSERT_NOT_NIL uses NSAssert to help with debugging. And these checks are generally in places where the code should never be called in the first place. If it is called, not having the bridge and failing silently could lead to unexpected behavior, no?
|
|
||
| let sdkConfigBuilder = try SDKConfigBuilder.create(from: config) | ||
|
|
||
| #if CIO_LOCATION_ENABLED |
There was a problem hiding this comment.
How do customers set this? In their Podfile?
getEnforcing throws at import time when the native module returns null, crashing the SDK for every user who hasn't enabled location. Switch to get() and silently no-op when the module is unavailable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, R8 minification fails on release builds when location is disabled because NativeLocationModule class references io.customer.location.* classes that are not on the runtime classpath with compileOnly. Also bumps iOS native SDK to 4.3.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…io-reactnative into feat/location-module
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
android/src/main/java/io/customer/reactnative/sdk/location/NativeLocationModule.kt
Show resolved
Hide resolved
mrehan27
left a comment
There was a problem hiding this comment.
Looks good overall. The only concern is that we probably shouldn't push all module initialization onto main thread, we can continue discussing this on slack.
| // Location is an optional module — NativeModule may be null when location is not enabled. | ||
| // Methods silently no-op when the native module is unavailable. | ||
| const withNativeModule = (fn: (native: CodegenSpec) => void): void => { | ||
| if (NativeModule) { |
There was a problem hiding this comment.
Shouldn't we log a warning here instead? These methods shouldn't be called if location module is disabled, and if they are called, location should probably be enabled.
There was a problem hiding this comment.
They shouldn't be called if the module isn't added but they will still be part of the API, so they can be called, that's why we are protecting against it
| static readonly clearIdentify: () => Promise<any>; | ||
| static readonly deleteDeviceToken: () => Promise<void>; | ||
| static readonly identify: ({ userId, traits, }?: IdentifyParams) => Promise<any>; | ||
| static readonly identify: (input?: IdentifyParams) => Promise<any>; |
There was a problem hiding this comment.
Did we really change this? 🤔
When a developer calls location APIs without enabling the native module, the calls silently no-op. This adds a one-time dev warning with actionable setup instructions for both iOS and Android. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…io-reactnative into feat/location-module
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## [6.3.0](6.2.0...6.3.0) (2026-03-11) ### Features * Location enrichment ([#570](#570)) ([b225608](b225608))
Summary
Adds support for the Customer.io Location module in the React Native SDK.
CustomerIOLocationclass withsetLastKnownLocation()andrequestLocationUpdate()APIsCioLocationTrackingModeenum (Off,Manual,OnAppStart) for configurationCioConfigvialocation.trackingModeOptional dependency
Location is fully opt-in. Customers who don't need it pay no cost:
locationsubspec in Podfile (pod 'customerio-reactnative/location'). Uses a Swift compiler flag (-DCIO_LOCATION_ENABLED) set by the subspec — deterministic, no#if canImportambiguity.customerio_location_enabled=truetogradle.properties. Controls aBuildConfigflag that gates all location code at build time. When disabled, R8 eliminates dead code entirely. No reflection.Note
Medium Risk
Adds a new optional native Location module and build-time gating on both Android and iOS, touching SDK initialization and dependency configuration. Risk is mainly around build configuration/packaging (R8, BuildConfig flags, CocoaPods subspecs) and ensuring apps not opting in don’t crash or bloat.
Overview
Adds opt-in Location enrichment support to the React Native SDK:
CioConfiggainslocation.trackingModeplus a newCustomerIO.locationAPI (setLastKnownLocation,requestLocationUpdate) andCioLocationTrackingModeenum.Implements a new Location TurboModule bridge (Android
NativeLocationModule+ iOSNativeLocationwith ObjC++ TurboModule wrapper) and wires it into SDK initialization only when enabled.Introduces platform-specific opt-in wiring: Android uses
customerio_location_enabled->BuildConfig.CIO_LOCATION_ENABLEDto conditionally includeio.customer.android:location(withconsumer-rules.proto avoid R8 missing-class failures), and iOS adds alocationsubspec that pullsCustomerIO/Locationand sets-DCIO_LOCATION_ENABLED. The example app is updated to enable location and request permissions, and Android SDK dependency is bumped to4.17.0.Written by Cursor Bugbot for commit 2256101. This will update automatically on new commits. Configure here.