Instant Journey presentation on Android
Prepare a Journey before it appears in Android View and Jetpack Compose apps.
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.
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.
Install
Add mavenCentral() to your dependency repositories, then install the core
View SDK and, for Compose apps, its adapter:
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.
@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:
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:
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.