This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build the library
./gradlew :analytics:build
# Run unit tests
./gradlew :analytics:test
# Run instrumented tests (requires Android device/emulator)
./gradlew :analytics:connectedAndroidTest
# Install to local Maven repository
./gradlew :analytics:install
# Clean build artifacts
./gradlew clean
# Generate Javadocs
./gradlew :analytics:androidJavadocs
# Run tests with coverage
./gradlew :analytics:createDebugCoverageReport
# Lint checks
./gradlew :analytics:lint
# AnimalSniffer (Java/Android API compatibility — fails on Java 9+ APIs and Android calls
# above minSdk that aren't SDK_INT-gated; report at analytics/build/reports/animalsniffer/)
./gradlew :analytics:animalsnifferRelease
# Build demo app
./gradlew :analytics:mixpaneldemo:build- No unit tests: The SDK uses instrumented tests only for real device validation
- Instrumented tests: Located in
/analytics/src/androidTest/(require Android device/emulator)- Use AndroidJUnit4 runner
- IMPORTANT: Run tests from the analytics module (not
:analytics:mixpaneldemo) using:analytics:connectedAndroidTest - Run all tests:
./gradlew :analytics:connectedAndroidTest - Run specific test class:
./gradlew :analytics:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.mixpanel.android.mpmetrics.TestClassName - Run specific test method:
./gradlew :analytics:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.mixpanel.android.mpmetrics.TestClassName#testMethodName - Run multiple test methods:
./gradlew :analytics:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.mixpanel.android.mpmetrics.TestClassName#testMethod1,testMethod2 - BlockingQueue pattern for async testing
- TestUtils provides mock implementations
The Mixpanel Android SDK follows a producer-consumer pattern with persistent storage:
-
MixpanelAPI (Main Entry Point)
- Singleton access via
getInstance() - Handles event tracking, people properties, feature flags
- Thread-safe, supports multiple instances
- Singleton access via
-
AnalyticsMessages (Message Queue)
- Manages communication between user threads and background worker
- Batches events before sending to servers
- Implements retry logic and offline handling
-
MPDbAdapter (Persistence Layer)
- SQLite-based storage for events and people updates
- Handles offline queuing and data retrieval
-
PersistentIdentity (Identity Management)
- Manages distinct IDs, anonymous IDs, super properties
- Uses SharedPreferences for persistence
-
HttpService (Network Layer)
- Handles all HTTP communication with Mixpanel servers
- Supports GZIP compression
- Configurable timeouts and retry logic
- Events are batched and sent every 60 seconds or on app background
- SQLite database stores events when offline
- Feature flags are cached and refreshed periodically
- Automatic events track app lifecycle (configurable)
- ProGuard rules are provided in
analytics/proguard.txt
The project uses semantic versioning (X.Y.Z) and publishes to Maven Central:
- Version is defined in each module's
gradle.propertiesasVERSION_NAME(analytics:analytics/gradle.properties) - Release script:
./release.sh [version] - Published as:
com.mixpanel.android:mixpanel-android:X.Y.Z
The release.sh script handles the complete release workflow:
- Updates version in gradle.properties and README.md
- Builds and publishes artifacts to OSSRH staging
- Automatically uploads to Maven Central Portal (requires env vars)
- Creates git tag and updates documentation
- Updates to next snapshot version
Required Environment Variables:
export CENTRAL_PORTAL_TOKEN=<your-portal-username>
export CENTRAL_PORTAL_PASSWORD=<your-portal-password>The SDK publishes via the new Maven Central Portal:
-
Generate Portal Tokens:
- Log in to https://central.sonatype.com with your OSSRH credentials
- Navigate to your account settings
- Generate a user token (username and password pair)
- Store these securely in
~/.gradle/gradle.properties:centralPortalToken=<your-token-username> centralPortalPassword=<your-token-password> - Security Note: For enhanced security, consider using encrypted storage options instead of plain text:
- Environment variables:
export CENTRAL_PORTAL_TOKEN=... - gradle-credentials-plugin for encrypted storage
- System keychain integration (e.g., macOS Keychain, Windows Credential Store)
- CI/CD secret management systems
- Environment variables:
-
Publishing Process:
- Artifacts are uploaded to OSSRH staging API:
https://ossrh-staging-api.central.sonatype.com/ - The Portal upload is triggered via the manual API endpoint
- Deployments appear at https://central.sonatype.com/publishing/deployments
- Manual release to Maven Central is required from the Portal UI (unless using automatic publishing)
- Artifacts are uploaded to OSSRH staging API:
-
GitHub Actions:
- Use the
publish-maven.ymlworkflow for automated publishing - Portal tokens should be stored as repository secrets:
CENTRAL_PORTAL_TOKEN(the token username)CENTRAL_PORTAL_PASSWORD(the token password)
- Publishing types available:
user_managed: Manual release from Portal UI (default)automatic: Auto-release if validation passes
- Use the
-
Manual Portal Upload (if automation fails):
AUTH_TOKEN=$(echo -n "username:password" | base64) curl -X POST \ -H "Authorization: Bearer $AUTH_TOKEN" \ "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.mixpanel.android?publishing_type=user_managed"
- Min SDK: 21
- Target/Compile SDK: 34
- Android Gradle Plugin: 8.13.2
- Kotlin: 2.1.0
:analytics(analytics/) — mainmixpanel-androidSDK. Consumes:commonvia its published Maven coordinate (com.mixpanel.android:mixpanel-android-common:X.Y.Z), not as aproject(':common')dependency. This means:commonmust be released to Maven Central before the main SDK can pick up changes; the trade-off buys back independent snapshot publishing for:commonand matches the consumption pattern:openfeature-provideruses for the main SDK.:common— published ascom.mixpanel.android:mixpanel-android-common. HoldsMixpanelEventBridge(KotlinSharedFlowevent dispatcher for cross-SDK consumption) and a Kotlin JsonLogic implementation. Has its owngradle.propertiesand is versioned independently of the main SDK.:openfeature-provider— published ascom.mixpanel.android:mixpanel-android-openfeature. Consumes the main SDK via its published Maven coordinate.:analytics:mixpaneldemo(analytics/mixpaneldemo/) — sample app, not published.
For local iteration on :common against :analytics or :openfeature-provider, swap the Maven dep for the commented-out project(':...') line in the consumer's build script (same workflow :openfeature-provider/build.gradle.kts already uses).
- Single HandlerThread for background processing
- No Service components used
- Message-based communication between threads
- All public APIs are thread-safe
- Never crash the host app - catch all exceptions
- Silent failures with logging for non-critical errors
- Defensive null checking throughout
- Graceful degradation when features unavailable
- Package-private visibility by default for internal classes
- Member variables prefixed with 'm' (e.g.,
mContext) - Final fields for immutability
- Static nested classes for data models
- Synchronized blocks over synchronized methods
- Singleton with token-based instances
- Builder pattern for configuration (MixpanelOptions)
- Fluent interfaces for People/Group operations
- Progressive disclosure through method overloading
- Always use application context to prevent leaks
- Cursor and database cleanup in finally blocks
- Lazy initialization for expensive objects
- WeakReference for activity/callback references
- Instrumented tests only (no unit tests)
- BlockingQueue pattern for async verification
- Real database testing (not mocked)
- Thread safety validation in tests
- Runtime options (MixpanelOptions)
- AndroidManifest meta-data
- Compile-time defaults (MPConfig)
- Direct SQLite usage (no ORM)
- Enum-based table management
- Prepared statements for performance
- Automatic cleanup based on data age
- Ensured CLAUDE.md accurately reflects the most recent changes