@luciq/react-native
v19.10.1
Published
Luciq is the Agentic Observability Platform built for Mobile.
Readme
Our intelligent AI agents help you capture rich, contextual data for every issue, including full session replays, console logs, and detailed network requests, to proactively detect, prioritize, and resolve problems automatically.
Ship faster, deliver frustration-free user sessions, and focus on building what matters.
Table of Contents
- Requirements
- Installation
- Initializing Luciq
- iOS Usage Descriptions
- Source Map Uploads for Crash Reports
- Network Logging
- Repro Steps
- Custom Spans (APM)
- TypeScript
- Adding a new feature
- Need Help?
- Contact Us
Requirements
- React Native
>= 0.72.3 - iOS
>= 13.4 - Android
minSdkVersion >= 21
Installation
Install the package:
npm install @luciq/react-native # or yarn add @luciq/react-nativeExpo only. Add the Luciq config plugin to
app.json:{ "expo": { "plugins": [ [ "@luciq/react-native", { "addScreenRecordingBugReportingPermission": true } ] ] } }addScreenRecordingBugReportingPermissionis optional — whentrue, the plugin adds the iOS microphone & photo-library usage descriptions and the AndroidFOREGROUND_SERVICE_MEDIA_PROJECTIONpermission required for screen recording in bug reports.iOS only. Install CocoaPods:
cd ios && pod install && cd ..
Initializing Luciq
Call Luciq.init once, as early as possible — at the top of your entry file (index.js or App.tsx), outside any component. InvocationEvent is a named export, not a property on the default Luciq namespace.
import Luciq, { InvocationEvent } from '@luciq/react-native';
Luciq.init({
token: 'APP_TOKEN',
invocationEvents: [InvocationEvent.shake],
});You can combine multiple invocation events:
Luciq.init({
token: 'APP_TOKEN',
invocationEvents: [
InvocationEvent.shake,
InvocationEvent.screenshot,
InvocationEvent.floatingButton,
],
});Available InvocationEvent values: shake, screenshot, twoFingersSwipe, floatingButton, none.
Find your app token in your Luciq dashboard under Settings → SDK Integration.
iOS Usage Descriptions
Luciq needs microphone access to capture audio during screen recordings, and photo-library access to let users attach images to bug reports. Apple rejects apps that omit usage descriptions for either, so add the following keys to your app's Info.plist:
NSMicrophoneUsageDescriptionNSPhotoLibraryUsageDescription
Suggested copy:
- "
<app name>needs microphone access to record audio with screen recordings attached to bug reports." - "
<app name>needs photo-library access to attach images to bug reports."
The permission prompts only appear when a user actually starts a screen recording or attaches a photo from the Luciq UI.
Source Map Uploads for Crash Reports
For your app crashes to show fully symbolicated stack traces, the build scripts in @luciq/react-native will generate and upload source maps to your dashboard on release builds. The uploader reads your app token from the Luciq.init({ token: 'YOUR_APP_TOKEN' }) call in JavaScript.
If your token is defined as a constant or imported from elsewhere, override the lookup with environment variables:
| Variable | Purpose |
| --------------------------------- | ------------------------------------- |
| LUCIQ_APP_TOKEN | App token used for the upload |
| LUCIQ_APP_VERSION_NAME | Overrides the inferred versionName |
| LUCIQ_APP_VERSION_CODE | Overrides the inferred versionCode |
| LUCIQ_SOURCEMAPS_UPLOAD_DISABLE | Set to TRUE to skip the upload step |
Network Logging
Network logging is enabled by default. It intercepts fetch and XMLHttpRequest calls and attaches them to outgoing reports. Disable it with:
import { NetworkLogger } from '@luciq/react-native';
NetworkLogger.setEnabled(false);Repro Steps
Luciq Repro Steps record the screens a user visits. Each screen is attached to a bug report when it's sent. Repro Steps are enabled by default.
React Navigation
v5+ — pass Luciq.onStateChange to your NavigationContainer:
import { NavigationContainer } from '@react-navigation/native';
import Luciq from '@luciq/react-native';
<NavigationContainer onStateChange={Luciq.onStateChange}>{/* ... */}</NavigationContainer>;v4 and below — wire Luciq.onNavigationStateChange to the root app:
export default () => <App onNavigationStateChange={Luciq.onNavigationStateChange} />;React Native Navigation (Wix)
Register Luciq.componentDidAppearListener:
import { Navigation } from 'react-native-navigation';
import Luciq from '@luciq/react-native';
Navigation.events().registerComponentDidAppearListener(Luciq.componentDidAppearListener);Manual screen reporting
For custom navigation, report screen changes yourself:
Luciq.reportScreenChange('CheckoutScreen');Disabling Repro Steps
import Luciq, { ReproStepsMode } from '@luciq/react-native';
Luciq.setReproStepsConfig({ all: ReproStepsMode.disabled });ReproStepsMode values: enabled, enabledWithNoScreenshots, disabled.
Custom Spans (APM)
Custom spans let you manually instrument arbitrary code paths for performance tracking — useful for operations that aren't covered by the automatic instrumentation.
Start / end a span
import { APM } from '@luciq/react-native';
const span = await APM.startCustomSpan('Load User Profile');
if (span) {
try {
await loadUserProfile();
} finally {
await span.end();
}
}Record a completed span
import { APM } from '@luciq/react-native';
const start = new Date(Date.now() - 1500);
const end = new Date();
await APM.addCompletedCustomSpan('Cache Lookup', start, end);Behavior
- Limit: up to 100 concurrent spans at a time.
- Name length: truncated to 150 characters; empty names are rejected.
- Timestamps:
endDatemust be afterstartDate; otherwise the span is rejected. - Idempotent: calling
span.end()more than once is safe. - Gating: spans are only created when the SDK is initialized, APM is enabled, and the custom-spans feature is enabled for your account.
API reference
APM.startCustomSpan(name: string): Promise<CustomSpan | null>
Starts a custom span. Returns the span object, or null if the span couldn't be created (e.g. feature disabled, span cap reached, invalid name).
CustomSpan.end(): Promise<void>
Ends the span and reports it. Idempotent.
APM.addCompletedCustomSpan(name: string, startDate: Date, endDate: Date): Promise<void>
Records a span whose start and end times are already known.
TypeScript
The package ships with type definitions. The public surface is exposed as:
Luciq(default export) — top-level SDK actions:init,onStateChange,onNavigationStateChange,componentDidAppearListener,reportScreenChange,setReproStepsConfig, and many more.- Named modules:
APM,BugReporting,CrashReporting,FeatureRequests,NetworkLogger,Replies,SessionReplay,Surveys. - Named enums:
InvocationEvent,ReproStepsMode,LogLevel,NetworkInterceptionMode,Locale,ColorTheme,WelcomeMessageMode,ExtendedBugReportMode,NonFatalErrorLevel, and more. - Named types:
LuciqConfig,ThemeConfig,NetworkData,Survey,SessionMetadata.
import Luciq, {
APM,
BugReporting,
CrashReporting,
InvocationEvent,
NetworkLogger,
ReproStepsMode,
type LuciqConfig,
} from '@luciq/react-native';Adding a new feature
The library supports both the old bridge architecture and the new architecture (TurboModules) from a single source tree. Adding a new API — either a new method on an existing module or a brand-new module — requires touching the JS layer, the Codegen spec, and both native platforms in lockstep so that the types stay aligned.
Adding a method to an existing module
- Codegen spec — add the method signature to
src/specs/Native<Module>.ts. Signatures here must use Codegen-compatible types (boolean,string,number/double,Array<...>,Object,Promise<T>,void). Avoidint— usedouble. Callbacks for spec methods are typed as(value: Object) => void. - JS native wrapper — mirror the method on the
NativeModuleinterface insrc/native/Native<Module>.ts. This is the richly-typed surface (enum types, unions) that the JS modules consume; the spec is the Codegen-safe subset. - JS module — expose the public API in
src/modules/<Module>.ts, calling throughNative<Module>.<method>(...). - Android — implement the method in
android/src/main/java/ai/luciq/reactlibrary/RNLuciq<Module>Module.java. The module extendsNative<Module>Spec; annotate with@ReactMethodand match the spec's parameter types exactly (e.g.doublenotint,Promisewhere the spec returnsPromise<T>). - iOS — implement the method in
ios/RNLuciq/Luciq<Module>Bridge.mmusingRCT_EXPORT_METHOD. The bridge file extension is.mm(Objective-C++) so the same source compiles on both architectures. - Tests — add coverage in
test/modules/<Module>.spec.tsandandroid/src/test/java/ai/luciq/reactlibrary/RNLuciq<Module>ModuleTest.java.
Adding a brand-new module
In addition to the steps above, register the new module in each arch-specific location:
- Codegen spec — create
src/specs/Native<Module>.tsand export the module viaTurboModuleRegistry.get<Spec>('LCQ<Module>'). Keep theLCQprefix — it must match the iOSRCT_EXPORT_MODULEname and the AndroidNAMEconstant. - JS native wrapper — create
src/native/Native<Module>.tswith a fallback:(TurboSpec ?? ReactNativeModules.LCQ<Module>) as unknown as <Module>NativeModule. Add the module to theLuciqNativePackageinterface insrc/native/NativePackage.ts. - JS module and public export — create
src/modules/<Module>.tsand re-export it fromsrc/index.ts. - Android Codegen spec — add
android/src/oldarch/java/ai/luciq/reactlibrary/Native<Module>Spec.javaextendingReactContextBaseJavaModule, withpublic static final String NAME = "LCQ<Module>". The newarch variant is generated intobuild/generated/source/codegen/javaby React Gradle Plugin at build time, so nothing is hand-authored underandroid/src/newarch/. - Android module — create
android/src/main/java/ai/luciq/reactlibrary/RNLuciq<Module>Module.javaextendingNative<Module>Spec, and register it inRNLuciqReactnativePackage.java(which extendsTurboReactPackage): add acase "LCQ<Module>"returning a new instance ingetModule(...), and a matchingReactModuleInfoentry ingetReactModuleInfoProvider(). TheisTurboModuleflag is driven byBuildConfig.IS_NEW_ARCHITECTURE_ENABLED, so the same registration works on both architectures. - iOS bridge — create
ios/RNLuciq/Luciq<Module>Bridge.h/.mm. UseRCT_EXPORT_MODULE(LCQ<Module>)so the name resolves on both architectures.
Running Codegen locally
Codegen runs automatically during the example app's build. To regenerate manually from the library root:
cd examples/default/android && ./gradlew generateCodegenArtifactsFromSchema
cd examples/default/ios && RCT_NEW_ARCH_ENABLED=1 pod installIf the JS spec and the native implementation disagree on a type, Codegen will fail the build with a mismatch error — that's the signal to bring the three layers back in sync.
Need Help?
🌐 Visit our website • 📖 Read the docs • 💬 Get help
Contact Us
Primary Contact Email: [email protected]
LinkedIn: linkedin.com/company/luciq
