Contentful Personalization & Analytics
    Preparing search index...
    Contentful Logo

    Android SDK Reference Implementation

    Readme · Guides · Reference · Contributing


    Caution

    Pre-release. API surface is not yet stable.

    This is the native Android reference implementation for the Contentful Optimization Android SDK. It demonstrates the integration pattern for both Jetpack Compose (:compose) and XML Views (:views), and is the target for the shared Maestro E2E suite (see maestro/README.md).

    • OptimizationRoot initialization with mock server configuration
    • OptimizedEntry personalization with view and click tracking
    • Nested entry resolution and recursive rendering
    • Navigation with screen tracking via ScreenTrackingEffect
    • Live updates behavior: default (global), explicit live, and locked variants
    • PreviewPanelConfig preview panel with audience/variant override controls
    • Analytics event display for debugging tracked events
    • All accessibility identifiers aligned with the iOS SwiftUI implementation for cross-platform E2E parity

    The app defines one locale in shared config, passes it to the native SDK as top-level locale, and passes it directly to the raw CDA fetch helper. Entries passed to OptimizedEntry use the standard single-locale CDA entry shape. Do not use all-locale CDA responses or locale=*, because SDK entry resolution expects direct single-locale fields such as fields.nt_experiences and fields.nt_variants. See Locale handling in the Optimization SDK Suite for the broader locale model and Entry personalization and variant resolution for the entry contract.

    This mock app uses one Contentful locale. A production app can derive the application locale from its own navigation, i18n, or account-preference layer and pass that value to both SDK locale and CDA requests when they should stay aligned.

    • Android SDK with ANDROID_HOME set
    • Android emulator or connected device
    • adb in PATH
    • pnpm dependencies installed at monorepo root (pnpm install)
    • Android bridge built: pnpm --filter @contentful/optimization-js-bridge build

    From the monorepo root:

    pnpm install
    pnpm --filter @contentful/optimization-js-bridge build

    The bootstrap script starts the mock server, builds the app, and launches it on an emulator:

    cd implementations/android-sdk
    ./scripts/bootstrap.sh

    Or manually:

    # Terminal 1: Start mock server
    pnpm serve:mocks

    # Terminal 2: Build and install (the app reaches the host mock via 10.0.2.2 — no adb reverse needed)
    cd implementations/android-sdk
    ./gradlew :compose:assembleDebug
    adb install -r compose/build/outputs/apk/debug/compose-debug.apk
    adb shell am start -n com.contentful.optimization.app/.MainActivity

    To launch with a clean SDK state (clears the persisted profile on cold start):

    adb shell am start -n com.contentful.optimization.app/.MainActivity --ez reset true
    

    Open this directory (implementations/android-sdk/) as an Android Studio project. After Gradle sync, build and launch either app on the selected device (MainActivity in :compose or :views), set breakpoints in the app or SDK source, and run the JVM unit tests from the gutter.

    Before running the app from the IDE, in a separate terminal:

    # From the monorepo root, build the bridge once (or after bridge source changes):
    pnpm --filter @contentful/optimization-js-bridge build

    # Then start the mock server and leave it running:
    pnpm --dir lib/mocks serve

    The E2E suite is Maestro, run from the command line rather than an IDE run configuration — pnpm test:e2e (both apps) or see maestro/README.md.

    Use this app when you need a debuggable native Android surface for changes in packages/android/ContentfulOptimization or the shared JS bridge. The Gradle project includes the SDK module from the workspace through a composite build, so app builds compile the Kotlin source and package the local bridge asset rather than a published AAR.

    The normal loop is:

    1. Edit Kotlin in packages/android/ContentfulOptimization/src/main/kotlin/... or bridge TypeScript in packages/universal/optimization-js-bridge/src/....

    2. Build the changed app or both app shells from implementations/android-sdk/:

      ./gradlew :compose:assembleDebug :views:assembleDebug
      
    3. Run the Compose or Views app locally, then validate with the matching Maestro flow.

    If bridge source changed, rebuild the bridge before treating app results as meaningful:

    pnpm --filter @contentful/optimization-js-bridge build
    

    Run the smallest check that covers the changed surface:

    Change area Suggested validation
    Bridge TypeScript only pnpm --filter @contentful/optimization-js-bridge typecheck and pnpm --filter @contentful/optimization-js-bridge build
    Kotlin SDK or UI adapter behavior ./gradlew :compose:assembleDebug :views:assembleDebug
    Compose or Views user flow pnpm test:e2e:compose -- --flow <suite> or pnpm test:e2e:views -- --flow <suite>
    Shared preview-panel behavior Run the affected Maestro suite against both apps
    Documentation-only README changes Prettier on touched Markdown and git diff --check

    Common local pitfalls:

    • Keep the Compose and Views apps in lock-step. New screens, controls, and test identifiers must exist in both app shells.
    • The apps reach the host mock through http://10.0.2.2:8000; no manual adb reverse setup is required for normal local runs.
    • The old UiAutomator module is dormant. Add or update Maestro flows instead.
    • After switching branches, force a bridge rebuild if the copied Android UMD asset may not match the checked-out bridge source.
    • If an E2E regression appears in only one app shell, check app test-tag parity before changing SDK behavior.