# Instant Journey presentation on Android (https://www.getfounderhq.com/docs/journeys/mobile-preparation-android)

Prepare a published Journey while the preceding screen is visible, then present
it without waiting for a configuration request or a new renderer. FounderHQ
retains one renderer for each host and keeps preparation free of impressions,
answers, and completion events.

<Callout type="info" title="Before you start">
Install version **0.8.1** or later. Preparation arrived in 0.7.0. Existing
`load(...)` calls keep working: they prepare and present in one operation.
Adopt the persistent host APIs below when presentation latency matters.
</Callout>

## Install

Add `mavenCentral()` to your dependency repositories, then install the core
View SDK and, for Compose apps, its adapter:

```kotlin
implementation("com.getfounderhq:journeys:0.8.1")
implementation("com.getfounderhq:journeys-compose:0.8.1")
```

The SDK supports Android API 24 and later.

## Jetpack Compose

Place one `JourneyHost` at a stable point in the composition. Keep it composed
while navigation changes the surrounding screen.

```kotlin
@Composable
fun OnboardingEntry() {
    val journeyState = rememberJourneyState()

    Box(Modifier.fillMaxSize()) {
        Button(onClick = journeyState::present) {
            Text("Start onboarding")
        }

        JourneyHost(
            configuration = JourneyConfiguration(
                apiKey = "fhq_pk_XXXX",
                journeyId = "journey_123",
                identity = JourneyIdentity(externalId = "usr_1042"),
            ),
            state = journeyState,
            modifier = Modifier.fillMaxSize(),
            listener = object : JourneyListener {
                override fun onEvent(event: JourneyEvent) {
                    if (event.type == JourneyEventType.COMPLETE) {
                        journeyState.dismiss()
                    }
                }
            },
        )
    }
}
```

`JourneyHost` begins preparation when it enters the composition. Observe
`journeyState.readiness` or `journeyState.isPrepared` when the surrounding UI
needs to reflect readiness. `present()` shows the prepared renderer. `dismiss()`
hides it and resets that same renderer with a fresh client session, so the next
presentation does not show completed steps or previous answers.

Call `journeyState.prepare()` to retry a failed preparation or ask the retained
host to refresh. Call `journeyState.dispose()` only when the owning flow is
finished; otherwise keep the host composed and use `dismiss()`.

## Android Views

Keep one `JourneyView` attached to the host Activity or Fragment and use the
same lifecycle directly:

```kotlin
val journeyView = JourneyView(requireContext())

journeyView.prepare(
    JourneyConfiguration(
        apiKey = "fhq_pk_XXXX",
        journeyId = "journey_123",
        identity = JourneyIdentity(externalId = "usr_1042"),
    ),
    listener,
)

startButton.setOnClickListener {
    journeyView.present()
}

closeButton.setOnClickListener {
    journeyView.dismiss()
}
```

Call `dispose()` when the host is permanently destroyed. The View also cleans
up with its lifecycle owner and releases a hidden prepared renderer under
memory pressure. Changing identity invalidates the renderer before another
Journey can start.

## Readiness, refresh, and retries

Preparation fetches the published configuration while the shared renderer
loads in parallel. The server controls the refresh interval, which the SDK
clamps to 10–30 minutes. A successfully prepared configuration can start for up
to 30 minutes. A failed refresh does not extend that window; the SDK retries
only after its backoff when the user presents again, explicitly retries, or the
app next enters the foreground. It does not run a repeated network retry loop.
HTTP 429 responses honor `Retry-After`, and manual retry stays disabled until
that delay has passed.

The configuration and revision stay fixed while a Journey is visible. A newly
published revision becomes the next prepared presentation after the active one
is dismissed. Capture batches keep the revision, client session, identity, and
queue metadata assigned when each event was created, including batches sent
after an app restart.

An authorization denial immediately ends interaction, discards prepared state,
and suppresses automatic requests. **Try again** or an explicit `prepare()` / `present()`
performs a fresh authorization request with the same configuration, so a
republished Journey or restored entitlement can recover without rebuilding its
host.

Renderers that do not advertise preparation support use the compatible direct
path. They initialize only after `present()` and never emit hidden analytics.

## Loading and error UI

The default presentation shows an accessible, theme-aware progress ring without
text. Reduced motion uses a static ring. If the Journey is not rendered within
15 seconds of foreground time, the user sees a restrained status icon,
**Unable to load. Please try again.**, and an explicit **Try again** button. The
button stays disabled only while a request is active or a server-directed
backoff is pending.

View apps can replace both surfaces:

```kotlin
journeyView.loadingViewFactory = { context -> MyLoadingView(context) }
journeyView.errorViewFactory = { context, error, retry ->
    MyJourneyErrorView(context).apply {
        setRetryAction(retry)
    }
}
```

Compose apps set these factories through `JourneyHost(configureView = { ... })`.
Keep custom loading and retry controls accessible, and call the supplied retry
function instead of creating a second host.
