Guide for AI Coding Agents to Integrate Luciq on Android
⚠️ UNIVERSAL EXECUTION RULES ⚠️
Critical Rules - Apply to ALL Platforms:
- NEVER skip WAIT instructions - always get user confirmation before proceeding
- NEVER auto-execute optional steps - user must explicitly select them
- ALWAYS fetch latest SDK version before integration (DO NOT use placeholders like 1.0.0)
- ALL API parameters must be included (can be nil/null) - never omit parameters
Execution Guidelines:
- Be specific and concise - save tokens by being to the point
- Don't create documentation files - only code files required for integration
- Implement mandatory steps sequentially - complete each before moving to the next
- Prompt user with current step - describe what's happening, then proceed when done
- Only ask for optional steps after mandatory ones - show numbered list for selection
- Use numbered lists for all options - makes selection easier (e.g., "Select 1", "Select 2")
- Add wrap up step - execute validation only when user selects "Wrap up & validate"
- Check documentation first - confirm API availability before getting creative beyond examples
Official Documentation:
- Main docs: https://docs.luciq.ai/
- Always check official docs when unsure about API signatures or platform specifics
Integration Workflow Overview
┌─────────────────────────────────────────────────────────────┐
│ MANDATORY STEPS │
├─────────────────────────────────────────────────────────────┤
│ Step 1: Collect Required Information │
│ ├─ 1A: Get App Token (MCP or Manual) │
│ └─ 1B: Determine Integration Method │
│ │
│ Step 2: Add SDK Dependency (Platform-Specific) │
│ └─ Fetch latest version first! │
│ │
│ Step 3: Initialize the SDK (Platform-Specific) │
│ └─ Configure invocation events │
│ │
│ 🛑 STOP - Mandatory steps complete │
├─────────────────────────────────────────────────────────────┤
│ OPTIONAL STEPS │
├─────────────────────────────────────────────────────────────┤
│ User selects from menu (WAIT for selection): │
│ 1. Configure Network Logging │
│ 2. Mask Repro Step Screenshots │
│ 3. Add User Identification │
│ 4. Wrap up & validate │
├─────────────────────────────────────────────────────────────┤
│ VERIFICATION │
├─────────────────────────────────────────────────────────────┤
│ Step 4: Build → Test → Verify Dashboard │
└─────────────────────────────────────────────────────────────┘
Step 1 — Collect Required Information [MANDATORY]
1A: Get App Token
Check MCP Server First:
IF Luciq MCP server is installed:
1. Fetch all tokens with app names using MCP tools
2. Display tokens to user in numbered list with app names
3. Ask: "Which app token would you like to use?"
4. WAIT for selection
5. Store selected token
ELSE:
1. Display to user: "To integrate Luciq SDK, you'll need your App Token."
2. Display to user:
"📍 Find your token: Luciq Dashboard → Settings → SDK Integration"
3. Display on new line with indentation:
" Dashboard URL: https://dashboard.luciq.ai"
4. Ask: "Please provide your Luciq App Token:"
5. WAIT for user to provide token
6. Validate token format (non-empty, alphanumeric)
7. Store token for later use
END IF
Validation:
- Token should be non-empty
- Typically 32-40 character hexadecimal string
- If invalid format, warn user but proceed
1B: Determine Integration Method
Auto-Detection Logic:
Platform-specific detection should look for:
- iOS:
Podfile,Cartfile, SPM packages in.xcodeproj, or none → Manual - Android:
build.gradlewith repositories,pom.xml, or none → Manual
DETECT package managers in project:
- Count how many are found
IF exactly ONE package manager detected:
1. Inform user: "Detected [PackageManager], will use this for integration"
2. Store integration_method
3. Proceed to Step 2
ELSE IF multiple or zero detected:
1. Display platform-specific menu (see platform guides)
2. WAIT for user selection
3. Store integration_method
4. Proceed to Step 2
END IF
1C: Choose Configuration Mode
Ask User:
"How would you like to configure the SDK?"
Options:
1. Default configuration (fastest setup)
2. Custom configuration (full control)
WAIT for selection
Store selection as config_mode (default/custom)
If config_mode == default:
Inform user: "Using default configuration:
• Invocation: shake
• Network logging: enabled
• Network masking: common sensitive fields
• Auto masking: all types (text inputs, labels, media)
• APM (Android): enabled
• Repro steps: default SDK settings
• User identification: skip (can add later)"
Store the following:
- invocation_events = [shake]
- network_logging_enabled = true
- network_masking_enabled = true
- predefined_headers_to_mask = ["Authorization", "Cookie", "X-API-Key", "token"]
- predefined_body_fields_to_mask = ["password", "token", "ssn"]
- auto_masking_types = [textInputs, labels, media] (all three types)
- repro_steps_config = skip (use defaults)
- user_identification = skip
FOR ANDROID ONLY:
- apm_enabled = true
- Continue to Step 1D (Compose detection)
FOR iOS:
- Proceed to Step 2
FOR CUSTOM MODE:
- Continue with platform-specific interactive steps
If config_mode == custom:
- Continue with existing flow (Step 1C/1D for Android, Step 2 for iOS)
Step 2 — Add SDK Dependency [MANDATORY - Platform-Specific]
Pre-Dependency Critical Step:
⚠️ MUST FETCH LATEST VERSION FIRST:
1. Fetch latest version from platform-specific GitHub releases:
- iOS: https://github.com/luciqai/luciq-ios-sdk/releases/latest
- Android: http://api.github.com/repos/luciqai/luciq-android-sdk/releases/latest
2. Extract version number (e.g., "19.1.0")
3. Store version for dependency configuration
4. DO NOT use placeholder versions like "1.0.0" or "latest"
Then proceed to platform-specific dependency installation (see platform guides)
Step 3 — Initialize the SDK [MANDATORY - Platform-Specific]
Invocation Events Configuration
Check Configuration Mode:
IF config_mode == default:
Use predefined invocation events: [shake]
Skip to platform-specific initialization
ELSE IF config_mode == custom:
Continue with interactive prompts below
END IF
For Custom Mode - Ask User:
"Would you like to initialize the SDK with default invocation events (shake and screenshot)?"
Options: yes / no
WAIT for response
If YES:
- Use default:
[shake, screenshot] - See platform guide for syntax
If NO:
"Which invocation events (1 or more) should trigger Luciq?"
Options (select all that apply):
- shake: Device shake gesture
- screenshot: Screenshot capture
- floatingButton: Persistent floating button overlay
- none: Manual invocation only (via code)
WAIT for selection
Invocation Event Mapping:
none→ Empty array[]- Single selection →
[selected] - Multiple →
[event1, event2, ...]
Apply configuration in platform-specific syntax (see platform guides)
🛑 MANDATORY STEPS COMPLETE - STOP HERE
Optional Steps Menu
Check Configuration Mode First:
IF config_mode == default:
Auto-apply the following configurations:
1. Configure Network Logging → Apply predefined masking + add interceptor (Android: manual OkHttp required)
2. Mask Repro Step Screenshots → Apply all types masking
3. Configure Repro Steps Mode → Skip (use SDK defaults)
4. Add User Identification → Skip
After auto-configuration, display:
"Default configuration applied!
Optional: Would you like to customize any settings?
1. Modify network logging
2. Modify screenshot masking
3. Configure repro steps mode
4. Add user identification
5. Wrap up & validate (build and test)
Enter number or 'skip' to finish"
WAIT for selection
ELSE IF config_mode == custom:
Display standard menu:
"Luciq SDK core integration complete!
Optional configurations:
1. Configure Network Logging (intercept & mask sensitive data)
2. Mask Repro Step Screenshots (auto-blur sensitive UI elements)
3. Configure Repro Steps Mode (control screenshot inclusion per product)
4. Add User Identification (link reports to users)
5. Wrap up & validate (build and test)
Which would you like to configure? (Enter number or 'skip' to finish)"
WAIT for selection
END IF
DO NOT proceed with ANY optional step until user selects it
Optional Step 1 — Configure Network Logging
Concepts (Platform-Agnostic):
Purpose: Automatically capture network requests/responses and optionally mask sensitive data
Part A: Network Capture
Display options:
1. Keep automatic network capture enabled (default)
2. Disable automatic network capture
WAIT for selection
IF option 2:
- Apply platform-specific disable code (see platform guide)
- END
ELSE IF option 1:
- Continue with default (enabled)
END IF
Part B: Mask Sensitive Data
Display: "Specify which fields to mask:
Options:
1. Enter comma-separated field names
2. Skip (no masking)
For option 1, specify:
- Headers (e.g., Authorization, Cookie, X-API-Key)
- Body fields (e.g., password, token, ssn)"
WAIT for input
IF option 1 selected and input provided:
Parse input:
- Split by comma
- Trim whitespace
- Create arrays: headersToMask[], bodyFieldsToMask[]
Apply platform-specific masking code (see platform guide):
- Use loops for headers
- Use recursion for nested body fields
- Handle arrays and nested objects
ELSE IF option 2:
- Leave network logs unmasked
END IF
Implementation Requirements:
- Masking code MUST be placed AFTER SDK initialization
- Headers: Simple loop through header names
- Body: Recursive function to handle nested JSON at any depth
- Performance: Use Set/HashSet for O(1) field lookup
Optional Step 2 — Mask Repro Step Screenshots
Concepts (Platform-Agnostic):
Purpose: Automatically blur/mask sensitive UI elements in screenshot repro steps
Display options - Which types of elements should be masked?
1. Text inputs only
2. Labels only
3. Images/media only
4. Text inputs + labels
5. All (text inputs + labels + media)
6. Custom selection
7. Skip (no screenshot masking)
WAIT for selection
IF option 7:
- Skip screenshot masking
- END
ELSE:
Map selection to mask types:
1 → [textInputs]
2 → [labels]
3 → [images]
4 → [textInputs, labels]
5 → [textInputs, labels, images]
6 → Ask for custom combination, then apply
Apply platform-specific masking API (see platform guide)
END IF
Available Mask Types:
- iOS:
.textInputs: Text input fields, password fields.labels: Text labels, buttons with text.media: Comprehensive masking (includes all types)
- Android:
MaskingType.TEXT_INPUTS: Text input fields, password fieldsMaskingType.LABELS: Text labels, buttons with textMaskingType.MEDIA: Images, media content
Optional Step 3 — Configure Repro Steps Mode
Concepts (Platform-Agnostic):
Purpose: Control which products include screenshots in their repro steps
Default Behavior:
- Bug Reporting: Screenshots enabled
- Crash Reporting: Screenshots disabled
- Session Replay: Screenshots enabled
Display: "Select products to ENABLE screenshots for:"
Options (multiple selection):
1. Bug Reporting
2. Crash Reporting
3. Session Replay
4. All products
5. None (disable screenshots for all)
6. Keep defaults (Bug Reporting + Session Replay with screenshots, Crash without)
Enter selections (e.g., "1,3" or "4"):
WAIT for input
IF option 6:
- Keep default configuration
- END
ELSE IF option 5 or "none" selected:
- Disable screenshots for all products
- Apply platform-specific configuration (see platform guide)
- END
ELSE:
Parse selections:
- Selected products → Enable with screenshots
- Non-selected products → Enable WITHOUT screenshots
Apply platform-specific configuration (see platform guide)
END IF
Issue Type Mapping:
- Bug Reporting →
.bug/IssueType.Bug - Crash Reporting →
.crash/IssueType.Crash - Session Replay →
.sessionReplay/IssueType.SessionReplay - All products →
.all/IssueType.All
Optional Step 4 — Add User Identification
Concepts (Platform-Agnostic):
Purpose: Link bug reports to specific user accounts for better tracking
Display options - How would you like to identify users?
1. Using email
2. Using user ID
3. Using both email and ID
4. Skip (no user identification)
WAIT for selection
IF option 4:
- Skip user identification
- END
ELSE:
Store user_id_method
Proceed to Step 3A and 3B
END IF
Step 3A: Identify Login Flows
Search Strategy:
- Search for authentication/login methods in codebase
- Look for successful login callbacks/handlers
- Identify where user data becomes available
Placement Rules:
- Add identification AFTER authentication succeeds
- Add BEFORE any navigation/routing
- Ensure user data (email/id/name) is available at this point
API Signature (All Platforms):
identifyUser(
id: String/null, // User ID - required param, can be null
email: String/null, // Email - required param, can be null
name: String/null // Name - required param, can be null
)
Examples by Selection:
- Option 1 (email):
identifyUser(null, user.email, user.name) - Option 2 (ID):
identifyUser(user.id, null, user.name) - Option 3 (both):
identifyUser(user.id, user.email, user.name)
Platform-Specific Syntax: See platform guides
Step 3B: Identify Logout Flows
Search Strategy:
- Search for logout/signout methods in codebase
- Look for session clearing logic
- Identify where user data is removed
Placement Rules:
- Add logout call BEFORE clearing user session/data
- Ensure it's called on all logout paths
API Signature (All Platforms):
logOut() / logout() // No parameters
Platform-Specific Syntax: See platform guides
Step 4 — Verification & Testing (Wrap up & validate) [USER-INITIATED ONLY]
⚠️ CRITICAL: This step is ONLY executed when the user explicitly selects "Wrap up & validate" from the optional steps menu. Do NOT run automatically after mandatory steps complete.
After Mandatory Steps Complete
Display this summary and menu:
✅ Luciq SDK integration complete!
Platform: <iOS/Android>
SDK Version: <version>
Integration Method: <SPM/Gradle/etc>
Configuration:
• App Token: <first 8 chars>...
• Invocation Events: <configured events>
• Network Logging: <enabled/disabled>
• Network Masking:
- Headers: <list or none>
- Body Fields: <list or none>
• Screenshot Masking: <types or none>
• User Identification: <configured or not configured>
Next Steps - Select an option:
1. Configure optional features
2. Wrap up & validate
WAIT for user selection. Do NOT proceed to build verification unless user selects option 2.
1. Build Verification [ONLY WHEN USER REQUESTS]
Execute platform-specific build command:
- iOS:
xcodebuild -project ... -scheme ... build - Android:
./gradlew assembleDebug
Expected Result: BUILD SUCCEEDED with no errors
Common Build Errors & Solutions:
| Error | Likely Cause | Solution |
|---|---|---|
| "no such module" / "unresolved import" | Wrong import statement | Check platform guide for correct import |
| "version not found" | Invalid SDK version | Verify version fetched from releases |
| "package not resolved" | Dependency not added | Re-run package manager sync |
2. Runtime Testing [MANUAL]
After successful build, instruct user to perform these steps:
1. Build and run the app on simulator/emulator or device
2. Trigger Luciq using configured invocation method:
- IF shake enabled: Shake the device/simulator
- IF screenshot enabled: Take a screenshot
- IF floatingButton enabled: Tap the floating button
3. Verify Luciq UI appears
4. Fill out and submit a test bug report
5. Check Luciq dashboard at https://dashboard.luciq.ai
- Verify report appears
- Verify user identification (if configured)
- Verify network logs (if configured)
3. Final Summary [DISPLAY AFTER BUILD SUCCESS]
Generate and display summary after successful validation:
✅ Luciq SDK successfully integrated and validated!
Platform: <iOS/Android>
SDK Version: <version>
Integration Method: <SPM/Gradle/etc>
Configuration:
• App Token: <first 8 chars>...
• Invocation Events: <configured events>
• Network Logging: <enabled/disabled>
• Network Masking:
- Headers: <list or none>
- Body Fields: <list or none>
• Screenshot Masking: <types or none>
• User Identification: <configured or not configured>
Build Status: ✅ SUCCEEDED
Next Steps:
• ✅ Build completed successfully
• Run the app and test SDK invocation
• Submit a test report
• Verify report in Luciq dashboard
Integration Complete - Ready for Testing!
Error Handling & Troubleshooting
Common Issues Across Platforms:
1. SDK Not Initializing:
- Verify token is correct
- Check initialization code placement (usually in app entry point)
- Ensure import statement is correct
2. Network Logging Not Working:
- Verify masking code placed AFTER SDK initialization
- Check that network interceptor is properly configured
- Ensure app has network permissions
3. User Identification Not Showing:
- Verify identifyUser called after successful login
- Check that logout is called on all logout paths
- Ensure parameters are not all null
4. Reports Not Appearing in Dashboard:
- Verify device/simulator has internet connection
- Check app token is correct
- Wait a few minutes for sync
- Check dashboard filters
Extension Points for Platform Guides
Platform-specific guides should include:
Required Sections:
- Platform-Specific Critical Rules (import naming, package names, etc.)
- Pre-Integration Checklist (platform-specific gotchas)
- Step 2 Implementation (dependency management code)
- Step 3 Implementation (SDK initialization code)
- Optional Step 1 Implementation (network logging code)
- Optional Step 2 Implementation (screenshot masking code)
- Optional Step 3 Implementation (user identification code)
- Step 4 Implementation (build commands, platform-specific testing)
Reference Format:
## Step X — [Title]
> 📋 See [common-workflow.md#step-x](common-workflow.md#step-x) for detailed workflow
### Platform-Specific Implementation
[Platform-specific code and instructions]Luciq SDK Integration - Android Guide
Purpose: Android-specific implementation details for Luciq SDK integration. This guide provides Android-specific code, configuration, and platform requirements. It references the common workflow for high-level steps - when concatenated with common-workflow.md, all references will be in the same document.
⚠️ ANDROID-SPECIFIC CRITICAL RULES ⚠️
Package and Import:
- Gradle Dependency:
implementation 'ai.luciq.library:luciq:<VERSION>' - Import Statement:
import ai.luciq.library.Luciq - Language: Code examples in Kotlin (Java equivalents straightforward)
- Minimum Requirements: compileSdkVersion 29 or above
- Android 15+ (API 35): Requires Luciq 13.4.0+ for 16KB page size support
Android-Specific Execution Rules:
- Initialize in Application class (not Activity)
- Network logging: ALWAYS requires manual OkHttp interceptor (even with APM enabled)
- APM provides: App launch, network performance, UI hangs, screen loading metrics
- APM plugin captures network data for performance monitoring, but interceptor still needed for bug reports
- Compose apps: Use
luciq-compose(no APM) orluciq-compose-apm(with APM) - At least email OR id required for user identification (both null = error)
- Use exact version in gradle (not
+or ranges) - ALL API parameters must be included (can be
null)
Official Android Documentation:
- Android Integration: https://docs.luciq.ai/docs/android-integration
- APM Migration: https://docs.luciq.ai/docs/android-luciq-migration
- APM Network Logging: https://docs.luciq.ai/docs/android-apm-network
- User Identification: https://docs.luciq.ai/docs/android-identify-user
- Network Logging: https://docs.luciq.ai/reference/network-logging-android
- Screenshot Masking: https://docs.luciq.ai/docs/android-repro-steps
ANDROID-SPECIFIC IMPLEMENTATION DETAILS
Pre-Integration Checklist
Before starting, verify you understand these Android-specific points:
- Must initialize in Application class (not Activity)
- Requires compileSdkVersion 29+
- APM (optional): Enables advanced performance monitoring
- Network logging: ALWAYS requires manual OkHttp interceptor (even with APM)
- APM plugin captures network data for performance monitoring only, not bug reports
- Compose apps: Require compose-specific plugins
- At least email OR id required in identifyUser
- Use exact version in gradle dependencies
Step 1 — Collect Required Information [MANDATORY]
Workflow: See common-workflow.md - Step 1
Android-Specific Implementation
Package Manager Detection:
Check for:
build.gradle/build.gradle.kts→ Gradlepom.xml→ Maven
Integration Method Menu (if multiple/none detected):
- Gradle (Recommended)
- Maven
1C: Choose Configuration Mode
Workflow: See common-workflow.md - Step 1C
For Android:
- If
config_mode == default: Setapm_enabled = true, then continue to Step 1D - If
config_mode == custom: Continue to Step 1D for interactive APM selection
1D: APM (Application Performance Monitoring)
Check Configuration Mode:
IF config_mode == default:
apm_enabled is already set to true
Skip APM prompt, continue to Step 1E (Compose detection)
ELSE IF config_mode == custom:
Ask User:
"Do you want to enable APM (Application Performance Monitoring)?
APM provides:
- App launch tracking
- Network performance monitoring
- UI hangs detection
- Screen loading metrics
- Custom traces & attributes
Options: yes / no
WAIT for response"
Store selection as `apm_enabled` (true/false)
END IF
1E: Jetpack Compose Detection
Auto-Detection:
Search for Compose usage:
- Check build.gradle for: androidx.compose dependencies
- Search code for: @Composable annotations
IF Compose detected:
Inform user: "Jetpack Compose detected - will add Compose integration"
Store compose_enabled = true
ELSE:
Ask: "Are you using Jetpack Compose?"
Options: yes / no
WAIT for response
Store compose_enabled based on response
END IF
Step 2 — Add the Luciq SDK Dependency [MANDATORY]
Workflow: See common-workflow.md - Step 2Fetch latest version from: http://api.github.com/repos/luciqai/luciq-android-sdk/releases/latest
Android Implementation
Check Configuration Mode:
IF config_mode == default:
Apply plugins automatically:
- Always: id("luciq")
- Always (Android default): id("luciq-apm")
- If compose_enabled: id("luciq-compose-apm")
ELSE IF config_mode == custom:
Apply plugins based on user selections
END IF
Part A: Gradle Plugins
If integration_method == gradle:
// In project-level build.gradle.kts or build.gradle
buildscript {
dependencies {
classpath("ai.luciq.library:luciq-plugin:<FETCHED_VERSION>")
}
}
// In app-level build.gradle.kts (Kotlin DSL)
plugins {
id("luciq")
// Add based on config_mode and selections:
IF config_mode == default OR apm_enabled: id("luciq-apm")
IF (config_mode == default OR apm_enabled) AND compose_enabled: id("luciq-compose-apm")
IF config_mode == custom AND compose_enabled AND NOT apm_enabled: id("luciq-compose")
}
// In app-level build.gradle (Groovy)
plugins {
id 'luciq'
// Add based on config_mode and selections:
IF config_mode == default OR apm_enabled: id 'luciq-apm'
IF (config_mode == default OR apm_enabled) AND compose_enabled: id 'luciq-compose-apm'
IF config_mode == custom AND compose_enabled AND NOT apm_enabled: id 'luciq-compose'
}Part B: Add Dependencies
Base Dependencies (Always Required):
// build.gradle.kts (Kotlin DSL)
dependencies {
implementation("ai.luciq.library:luciq:<FETCHED_VERSION>")
}
// build.gradle (Groovy)
dependencies {
implementation 'ai.luciq.library:luciq:<FETCHED_VERSION>'
}APM Dependencies (If config_mode == default OR apm_enabled == true):
// build.gradle.kts (Kotlin DSL)
dependencies {
implementation("ai.luciq.library:luciq:<FETCHED_VERSION>")
implementation("ai.luciq.library:luciq-apm:<FETCHED_VERSION>")
}
// build.gradle (Groovy)
dependencies {
implementation 'ai.luciq.library:luciq:<FETCHED_VERSION>'
implementation 'ai.luciq.library:luciq-apm:<FETCHED_VERSION>'
}Compose Dependencies (If compose_enabled == true):
// build.gradle.kts (Kotlin DSL)
dependencies {
implementation("ai.luciq.library:luciq:<FETCHED_VERSION>")
IF config_mode == default OR apm_enabled:
implementation("ai.luciq.library:luciq-compose-apm:<FETCHED_VERSION>")
ELSE:
implementation("ai.luciq.library:luciq-compose:<FETCHED_VERSION>")
}
// build.gradle (Groovy)
dependencies {
implementation 'ai.luciq.library:luciq:<FETCHED_VERSION>'
IF config_mode == default OR apm_enabled:
implementation 'ai.luciq.library:luciq-compose-apm:<FETCHED_VERSION>'
ELSE:
implementation 'ai.luciq.library:luciq-compose:<FETCHED_VERSION>'
}Maven (If integration_method == maven):
<dependency>
<groupId>ai.luciq.library</groupId>
<artifactId>luciq</artifactId>
<version><FETCHED_VERSION></version>
</dependency>
<!-- IF apm_enabled -->
<dependency>
<groupId>ai.luciq.library</groupId>
<artifactId>luciq-apm</artifactId>
<version><FETCHED_VERSION></version>
</dependency>
<!-- IF compose_enabled AND apm_enabled -->
<dependency>
<groupId>ai.luciq.library</groupId>
<artifactId>luciq-compose-apm</artifactId>
<version><FETCHED_VERSION></version>
</dependency>
<!-- IF compose_enabled AND NOT apm_enabled -->
<dependency>
<groupId>ai.luciq.library</groupId>
<artifactId>luciq-compose</artifactId>
<version><FETCHED_VERSION></version>
</dependency>Automatic Permissions: SDK adds these to AndroidManifest.xml automatically:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>Step 3 — Initialize the SDK [MANDATORY]
Workflow: See common-workflow.md - Step 3
Android Implementation
Part A: Detect or Create Application Class
Search for Existing Application Class:
1. Search project for classes extending Application
2. Check AndroidManifest.xml for android:name in <application> tag
IF Application class found:
Store application_class_path and application_class_name
Inform user: "Found existing Application class: <name>"
ELSE:
Inform user: "No Application class found - will create MyApplication"
Set application_class_name = "MyApplication"
END IF
Part B: Initialize Luciq SDK
If NO existing Application class - Create new:
package com.yourpackage // Use detected package
import android.app.Application
import ai.luciq.library.Luciq
import ai.luciq.library.invocation.LuciqInvocationEvent
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Luciq with configured events
Luciq.Builder(this, "<APP_TOKEN>")
.setInvocationEvents(<EVENTS>)
.build()
}
}Then register in AndroidManifest.xml:
<application
android:name=".MyApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<!-- ... activities ... -->
</application>If existing Application class - Update onCreate():
import ai.luciq.library.Luciq
import ai.luciq.library.invocation.LuciqInvocationEvent
class <ExistingApplicationClass> : Application() {
override fun onCreate() {
super.onCreate()
// ... existing initialization code ...
// Initialize Luciq with configured events
Luciq.Builder(this, "<APP_TOKEN>")
.setInvocationEvents(<EVENTS>)
.build()
}
}Part C: APM Configuration (If config_mode == default OR apm_enabled == true)
config_mode == default OR apm_enabled == true)If APM is enabled, configure APM plugin in build.gradle:
⚠️ IMPORTANT: The APM plugin's networkEnabled captures network data for APM performance monitoring only. You MUST still add the OkHttp interceptor manually (see Optional Step 1) for network logs to appear in bug reports.
// In app-level build.gradle.kts
luciq {
apm {
// Network performance monitoring (for APM only, not bug reports)
networkEnabled = true
IF config_mode == default:
captureHttpBodyEnabled = true // Capture HTTP body for performance data
// Fragment span tracking
fragmentSpansEnabled = true
// Additional APM settings (for custom mode)
// autoUITraceEnabled = true
}
// Optional: Enable debug logs
// setDebugLogsEnabled(true)
// IF compose_enabled:
// setCaptureComposeNavigationDestinations(true)
}
// In app-level build.gradle (Groovy)
luciq {
apm {
networkEnabled = true
IF config_mode == default:
captureHttpBodyEnabled = true
fragmentSpansEnabled = true
}
}⚠️ NEXT STEP REQUIRED: After configuring APM, you MUST proceed to Optional Step 1 to add the OkHttp interceptor. Without the interceptor, network logs will NOT appear in bug reports (they'll only be in the APM dashboard).
Invocation Events Syntax:
// Check config_mode and apply accordingly:
IF config_mode == default:
// Default mode: shake only
.setInvocationEvents(LuciqInvocationEvent.SHAKE)
ELSE IF config_mode == custom:
// Custom (shake + screenshot)
.setInvocationEvents(
LuciqInvocationEvent.SHAKE,
LuciqInvocationEvent.SCREENSHOT
)
// Single event
.setInvocationEvents(LuciqInvocationEvent.FLOATING_BUTTON)
// Multiple events
.setInvocationEvents(
LuciqInvocationEvent.SHAKE,
LuciqInvocationEvent.SCREENSHOT,
LuciqInvocationEvent.FLOATING_BUTTON
)
// None (manual only) - omit setInvocationEvents()
END IFAvailable Events:
LuciqInvocationEvent.SHAKE- Device shakeLuciqInvocationEvent.SCREENSHOT- Screenshot captureLuciqInvocationEvent.FLOATING_BUTTON- Floating button overlayLuciqInvocationEvent.NONE- Manual only
Optional Step 1 — Configure Network Logging
Workflow: See common-workflow.md - Optional Step 1
Android Implementation
Check Configuration Mode:
IF config_mode == default:
OkHttp interceptor must be added (required for bug report network logs)
Network masking will be applied with predefined keys
Proceed with interceptor configuration
ELSE IF config_mode == custom:
Ask user about network logging preferences
END IF
⚠️ CRITICAL: Network Logging Always Requires Manual Interceptor
Even with APM enabled, the OkHttp interceptor MUST be added manually for network logs to appear in bug reports. The APM plugin only captures network data for performance monitoring (APM dashboard), not for bug report network logs.
Network Logging Implementation (Required for Bug Reports)
Supported HTTP Clients:
- OkHttp (most common) - Recommended
- gRPC
- GraphQL (via OkHttp)
Part A: Add Interceptor Dependencies
For OkHttp:
// build.gradle.kts
dependencies {
implementation("ai.luciq.library:luciq:<VERSION>")
IF apm_enabled:
implementation("ai.luciq.library:luciq-apm-okhttp-interceptor:<VERSION>")
ELSE:
implementation("ai.luciq.library:luciq-with-okhttp-interceptor:<VERSION>")
}
// build.gradle (Groovy)
dependencies {
implementation 'ai.luciq.library:luciq:<VERSION>'
IF apm_enabled:
implementation 'ai.luciq.library:luciq-apm-okhttp-interceptor:<VERSION>'
ELSE:
implementation 'ai.luciq.library:luciq-with-okhttp-interceptor:<VERSION>'
}For gRPC:
// build.gradle.kts
dependencies {
implementation("ai.luciq.library:luciq:<VERSION>")
implementation("ai.luciq.library:luciq-apm-grpc-interceptor:<VERSION>")
}Part B: Configure Interceptors in Code
For OkHttp (Recommended):
// If APM enabled:
import ai.luciq.library.apm.okhttp.LuciqAPMOkhttpInterceptor
import ai.luciq.library.apm.okhttp.LuciqApmOkHttpEventListener
val luciqInterceptor = LuciqAPMOkhttpInterceptor()
val luciqEventListener = LuciqApmOkHttpEventListener()
val client = OkHttpClient.Builder()
.addInterceptor(luciqInterceptor)
.eventListener(luciqEventListener)
.build()
// If APM NOT enabled:
import ai.luciq.library.network.LuciqOkhttpInterceptor
val luciqInterceptor = LuciqOkhttpInterceptor()
val client = OkHttpClient.Builder()
.addInterceptor(luciqInterceptor)
.build()For GraphQL (Apollo Client with OkHttp):
// Same as OkHttp - add interceptor to OkHttpClient
// then pass to Apollo:
val apolloClient = ApolloClient.Builder()
.serverUrl("https://api.example.com/graphql")
.okHttpClient(client) // client with Luciq interceptor
.build()For gRPC:
import ai.luciq.library.apm.grpc.LuciqGrpcInterceptor
val channel = ManagedChannelBuilder
.forAddress("api.example.com", 443)
.intercept(LuciqGrpcInterceptor())
.build()Part C: Mask Sensitive Data (Optional)
Check Configuration Mode:
IF config_mode == default:
Apply predefined masking keys automatically:
- Headers: ["Authorization", "Cookie", "X-API-Key", "token"]
- Body fields: ["password", "token", "ssn"]
ELSE IF config_mode == custom:
Ask user for custom masking fields (see common-workflow.md)
END IF
Masking Implementation for OkHttp:
import ai.luciq.library.network.NetworkLogListener
import ai.luciq.library.network.NetworkLogSnapshot
import org.json.JSONObject
import org.json.JSONArray
// If APM enabled:
import ai.luciq.library.apm.okhttp.LuciqAPMOkhttpInterceptor
import ai.luciq.library.apm.okhttp.LuciqApmOkHttpEventListener
// If APM NOT enabled:
import ai.luciq.library.network.LuciqOkhttpInterceptor
class NetworkConfig {
// Predefined for default mode, or custom based on user input
private val headersToMask = IF config_mode == default:
listOf("Authorization", "Cookie", "X-API-Key", "token")
ELSE:
listOf(/* user provided headers */) // Custom user input
private val bodyFieldsToMask = IF config_mode == default:
setOf("password", "token", "ssn")
ELSE:
setOf(/* user provided fields */) // Custom user input
fun createOkHttpClient(): OkHttpClient {
// Choose interceptor based on APM status
val luciqInterceptor = IF apm_enabled:
LuciqAPMOkhttpInterceptor()
ELSE:
LuciqOkhttpInterceptor()
luciqInterceptor.registerNetworkLogsListener(object : NetworkLogListener {
override fun onNetworkLogCaptured(networkLog: NetworkLogSnapshot): NetworkLogSnapshot {
// Mask headers
headersToMask.forEach { header ->
networkLog.request?.headers?.get(header)?.let {
networkLog.request?.headers?.set(header, "*****")
}
}
// Mask request/response body fields
networkLog.request?.requestBody?.let {
networkLog.request?.requestBody = maskJsonFields(it, bodyFieldsToMask)
}
networkLog.response?.responseBody?.let {
networkLog.response?.responseBody = maskJsonFields(it, bodyFieldsToMask)
}
return networkLog
}
})
return OkHttpClient.Builder()
.addInterceptor(luciqInterceptor)
IF apm_enabled:
.eventListener(LuciqApmOkHttpEventListener())
.build()
}
// Recursive helper to mask fields at any depth
private fun maskJsonFields(jsonString: String, fieldsToMask: Set<String>): String {
return try {
val jsonObject = JSONObject(jsonString)
maskJsonObject(jsonObject, fieldsToMask).toString()
} catch (e: Exception) {
jsonString
}
}
private fun maskJsonObject(jsonObject: JSONObject, fieldsToMask: Set<String>): JSONObject {
val result = JSONObject()
jsonObject.keys().forEach { key ->
result.put(key, when {
fieldsToMask.contains(key) -> "*****"
else -> {
when (val value = jsonObject.get(key)) {
is JSONObject -> maskJsonObject(value, fieldsToMask)
is JSONArray -> maskJsonArray(value, fieldsToMask)
else -> value
}
}
})
}
return result
}
private fun maskJsonArray(jsonArray: JSONArray, fieldsToMask: Set<String>): JSONArray {
val result = JSONArray()
for (i in 0 until jsonArray.length()) {
result.put(when (val value = jsonArray.get(i)) {
is JSONObject -> maskJsonObject(value, fieldsToMask)
is JSONArray -> maskJsonArray(value, fieldsToMask)
else -> value
})
}
return result
}
}Key Points:
- Use
Set<String>for O(1) lookup performance - Recursive masking handles nested JSON at any depth
- Masks both request and response bodies
- Works with both APM and non-APM interceptors
Optional Step 2 — Mask Repro Step Screenshots
Workflow: See common-workflow.md - Optional Step 2
Android Implementation
Minimum SDK Version: 11.13.0 (Jetpack Compose: 13.4.0+)
Check Configuration Mode:
IF config_mode == default:
Apply all masking types automatically:
Luciq.setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS, MaskingType.LABELS, MaskingType.MEDIA)
ELSE IF config_mode == custom:
Ask user for masking preferences
END IF
import ai.luciq.library.Luciq
import ai.luciq.library.MaskingType
// Default mode: All types (maximum privacy)
IF config_mode == default:
Luciq.setAutoMaskScreenshotsTypes(
MaskingType.TEXT_INPUTS,
MaskingType.LABELS,
MaskingType.MEDIA
)
// Custom mode options:
ELSE IF config_mode == custom:
// Text inputs only
Luciq.setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS)
// Text inputs + labels
Luciq.setAutoMaskScreenshotsTypes(
MaskingType.TEXT_INPUTS,
MaskingType.LABELS
)
// Text inputs + labels + media
Luciq.setAutoMaskScreenshotsTypes(
MaskingType.TEXT_INPUTS,
MaskingType.LABELS,
MaskingType.MEDIA
)
// Labels only
Luciq.setAutoMaskScreenshotsTypes(MaskingType.LABELS)
// Media only
Luciq.setAutoMaskScreenshotsTypes(MaskingType.MEDIA)
// Disable auto masking
Luciq.setAutoMaskScreenshotsTypes(MaskingType.MASK_NOTHING)
END IFBuild-time Configuration (Alternative):
// In Application class onCreate()
Luciq.Builder(this, "APP_TOKEN")
.setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS, MaskingType.LABELS)
.build()Additional Privacy Controls:
// Mark specific views as private (takes precedence over auto masking)
Luciq.addPrivateViews(view1, view2, view3)
// Remove from private list
Luciq.removePrivateViews(view1, view2, view3)Available Masking Types:
MaskingType.TEXT_INPUTS- EditText, TextInputLayout, text input fieldsMaskingType.LABELS- TextView, Button text, labels, titlesMaskingType.MEDIA- ImageView, VideoView, images and videoMaskingType.MASK_NOTHING- Disable all auto masking
Key Notes:
- Private Views API takes precedence over Auto Masking
- Works with Bug Reporting, Crash Reporting, and Session Replay
- Screenshots are disabled by default for Crash Reporting
Optional Step 3 — Configure Repro Steps Mode
Workflow: See common-workflow.md - Optional Step 3
Android Implementation
Based on user selections, apply configuration:
// Example: User selected Bug Reporting and Session Replay (1,3)
val configurations = ReproConfigurations.Builder()
.setIssueMode(IssueType.Bug, ReproMode.EnableWithScreenshots)
.setIssueMode(IssueType.SessionReplay, ReproMode.EnableWithScreenshots)
.setIssueMode(IssueType.Crash, ReproMode.EnableWithNoScreenshots) // Not selected
.build()
Luciq.setReproConfigurations(configurations)
// Example: User selected All products (4)
val allConfig = ReproConfigurations.Builder()
.setIssueMode(IssueType.All, ReproMode.EnableWithScreenshots)
.build()
Luciq.setReproConfigurations(allConfig)
// Example: User selected only Crash Reporting (2)
val crashOnlyConfig = ReproConfigurations.Builder()
.setIssueMode(IssueType.Crash, ReproMode.EnableWithScreenshots)
.setIssueMode(IssueType.Bug, ReproMode.EnableWithNoScreenshots) // Not selected
.setIssueMode(IssueType.SessionReplay, ReproMode.EnableWithNoScreenshots) // Not selected
.build()
Luciq.setReproConfigurations(crashOnlyConfig)
// Example: User selected None (5)
val noneConfig = ReproConfigurations.Builder()
.setIssueMode(IssueType.All, ReproMode.EnableWithNoScreenshots)
.build()
Luciq.setReproConfigurations(noneConfig)Issue Type Options:
IssueType.Bug- Bug ReportingIssueType.Crash- Crash ReportingIssueType.SessionReplay- Session ReplayIssueType.All- All products
Key Points:
- Selected products →
ReproMode.EnableWithScreenshots - Non-selected products →
ReproMode.EnableWithNoScreenshots
Optional Step 4 — Add User Identification
Workflow: See common-workflow.md - Optional Step 3
Android Implementation - Login Flow
import ai.luciq.library.Luciq
class AuthManager {
fun signIn(email: String, password: String): Boolean {
// ... authentication logic ...
if (authenticationSuccessful) {
// Identify user AFTER successful auth, BEFORE navigation
Luciq.identifyUser(
user.name, // name (can be null)
user.email, // email (can be null)
user.id // id (can be null)
)
isAuthenticated = true
return true
}
return false
}
}API Signature:
Luciq.identifyUser(
name: String?, // Required parameter, can be null
email: String?, // Required parameter, can be null
id: String? // Required parameter, can be null
)⚠️ CRITICAL: At least email OR id must be non-null. All null throws error.
Examples:
- Email only:
Luciq.identifyUser(user.name, user.email, null) - ID only:
Luciq.identifyUser(user.name, null, user.id) - Both:
Luciq.identifyUser(user.name, user.email, user.id)
Additional User Data (optional):
// Set additional user context (max 1000 chars)
Luciq.setUserData("Premium user, subscription: pro")Android Implementation - Logout Flow
class AuthManager {
fun logout() {
// Call Luciq logout BEFORE clearing session
Luciq.logoutUser()
// Then clear user data
currentUser = null
isAuthenticated = false
sharedPreferences.edit().clear().apply()
}
}Note: logoutUser() only works if user was previously identified
Step 4 — Verification & Testing [WRAP UP & VALIDATE]
Workflow: See common-workflow.md - Step 4
Android Build Verification
./gradlew assembleDebugExpected: BUILD SUCCESSFUL
Android-Specific Build Errors:
| Error | Cause | Fix |
|---|---|---|
compileSdkVersion < 29 | SDK too old | Set compileSdkVersion to 29+ |
Unresolved reference: Luciq | Missing dependency | Sync gradle, verify version |
Duplicate class | Dependency conflict | Check: ./gradlew dependencies |
Android Runtime Testing
-
Install and run:
./gradlew installDebug -
Trigger Luciq:
- Shake: Shake device/emulator
- Screenshot: Power + Volume Down
- Floating Button: Tap button overlay
-
Verify Luciq UI appears
-
Submit test report
-
Check dashboard: https://dashboard.luciq.ai
Android-Specific Troubleshooting
Issue: "compileSdkVersion must be >= 29"
Fix: Update build.gradle:
android {
compileSdkVersion = 34 // or higher
}Issue: Network logs not appearing
Fix: Add luciq-with-okhttp-interceptor dependency and configure client
Issue: Application class not registered
Fix: Add android:name=".MyApplication" to <application> tag in manifest
Issue: User identification error
Fix: Ensure at least email OR id is non-null (not both null)
Issue: Screenshots not masking
Fix: Use setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS, MaskingType.LABELS)
Quick Reference
// IMPORTS
import ai.luciq.library.Luciq
import ai.luciq.library.invocation.LuciqInvocationEvent
import ai.luciq.library.MaskingType
// INITIALIZATION (in Application class)
Luciq.Builder(this, "APP_TOKEN")
.setInvocationEvents(LuciqInvocationEvent.SHAKE, LuciqInvocationEvent.SCREENSHOT)
.build()
// USER IDENTIFICATION
Luciq.identifyUser(user.name, user.email, user.id) // At least email or id non-null
// USER DATA
Luciq.setUserData("Additional context")
// LOGOUT
Luciq.logoutUser()
// SCREENSHOT AUTO MASKING
Luciq.setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS, MaskingType.LABELS)
// Or during initialization:
Luciq.Builder(this, "APP_TOKEN")
.setAutoMaskScreenshotsTypes(MaskingType.TEXT_INPUTS, MaskingType.LABELS)
.build()
// PRIVATE VIEWS
Luciq.addPrivateViews(view1, view2)
// NETWORK LOGGING (OkHttp - without APM)
val luciqInterceptor = LuciqOkhttpInterceptor()
luciqInterceptor.registerNetworkLogsListener(object : NetworkLogListener {
override fun onNetworkLogCaptured(log: NetworkLogSnapshot): NetworkLogSnapshot {
// Modify log
return log
}
})
val client = OkHttpClient.Builder()
.addInterceptor(luciqInterceptor)
.build()
// NETWORK LOGGING (OkHttp - with APM)
import ai.luciq.library.apm.okhttp.LuciqAPMOkhttpInterceptor
import ai.luciq.library.apm.okhttp.LuciqApmOkHttpEventListener
val luciqInterceptor = LuciqAPMOkhttpInterceptor()
val client = OkHttpClient.Builder()
.addInterceptor(luciqInterceptor)
.eventListener(LuciqApmOkHttpEventListener())
.build()Java Equivalents
All Kotlin examples have straightforward Java equivalents:
val→final- Lambda
{ }→ Anonymous inner class ?.let→ null check withif- Extension functions → Static utility methods
Refer to official docs for Java examples if needed.
Updated 4 days ago
