Skip to content

Preload Plan

Preload plans in advance and reuse them later without the internet during the app lifecycle.

Overview

  • Use ExpoFpPlan.preloader to preload plans.
  • A preloaded plan is represented by ExpoFpPreloadedPlanInfo.
  • You can obtain a presenter for a preloaded plan with getPreloadedPlanPresenter(...).
  • Recommended: Preload plans if you expect to reuse them multiple times in one session.
  • If a preloaded plan is reloaded with a new planLink, the link info is automatically updated in the stored ExpoFpPreloadedPlanInfo.

Lifecycle Management

Important: Preloaded plans are NOT automatically disposed when the Activity/Fragment is destroyed. This allows you to reuse the same presenter across multiple screens without reloading.

  • Preloaded plans persist in memory until you explicitly call disposePreloadedPlan() or removeAllPreloadedPlans().
  • You can safely attach and detach the same preloaded presenter to different Activities/Fragments.
  • All event callbacks will continue to work after reattaching a preloaded plan.
  • You are responsible for disposing preloaded plans when they are no longer needed to free memory.

Listener lifecycle

The SDK does not retain the message listener. A preloaded presenter reused across screens keeps delivering callbacks without re-registering the listener.

Keep your own strong reference to the listener for as long as you need callbacks.

If you need to remove the listener earlier, you can manually call removeMessageListener():

// Optional: detach the listener manually when no longer needed
presenter?.removeMessageListener()

Step 1. Preload from the Internet

val expoKey = "YourExpoKey"

val preloadedPlanInfo = ExpoFpPlan.preloader.preloadPlan(
    planLink = ExpoFpLinkType.ExpoKey(expoKey)
)

With additional parameters

val additionalParams = listOf(ExpoFpPlanParameter.NoOverlay(true), ExpoFpPlanParameter.HideHeaderLogo(true))
val locationProvider: IExpoFpLocationProvider = YourLocationProvider()
val messageListener: IExpoFpPlanMessageListener = YourMessageListener()

val preloadedPlanInfo = ExpoFpPlan.preloader.preloadPlan(
    planLink = ExpoFpLinkType.ExpoKey("YourExpoKey"),
    additionalParams = additionalParams,
    locationProvider = locationProvider,
    messageListener = messageListener
)

Step 2. Preload from Downloaded Plan

val preloadedPlanInfo = ExpoFpPlan.preloader.preloadPlan(
    planLink = ExpoFpLinkType.DownloadedPlanInfo(downloadedPlanInfo)
)

With additional parameters

val additionalParams = listOf(ExpoFpPlanParameter.NoOverlay(true), ExpoFpPlanParameter.HideHeaderLogo(true))
val locationProvider: IExpoFpLocationProvider = YourLocationProvider()
val messageListener: IExpoFpPlanMessageListener = YourMessageListener()

val preloadedPlanInfo = ExpoFpPlan.preloader.preloadPlan(
    planLink = ExpoFpLinkType.DownloadedPlanInfo(downloadedPlanInfo),
    additionalParams = additionalParams,
    locationProvider = locationProvider,
    messageListener = messageListener
)

Step 3. Manage Preloaded Plans

Get all preloaded plans:

val preloadedPlansInfo: List<ExpoFpPreloadedPlanInfo> =
    ExpoFpPlan.preloader.getPreloadedPlansInfo()

Get a presenter from a preloaded plan:

val presenter = ExpoFpPlan.preloader.getPreloadedPlanPresenter(preloadedPlanInfo)

If only one plan is preloaded, you can get it without parameters:

val presenter = ExpoFpPlan.preloader.getPreloadedPlanPresenter()


Step 4. Show Preloaded Plan

View-based UI

container.addView(presenter.getView())

Important:

A preloaded presenter renders the plan with a single engine, so the plan is visible on one screen at a time. Each getView() call returns a new host container; the plan content is shown in the most recently attached one. When the screen currently showing the plan is destroyed (for example, on back navigation), the content automatically returns to the previous screen that is still present — no re-attachment code is needed.

Call getView() once per screen and keep the result for that screen's lifetime. Do not call it repeatedly in onStart()/onResume(): every call creates a new container, and re-adding a new one on each lifecycle pass accumulates empty containers in your layout. Do not compare results of different calls by identity.

Jetpack Compose

AndroidView(
    factory = { presenter.getView() },
    modifier = Modifier.fillMaxSize()
)

Note: getView() returns a self-sizing host container, so you can host it directly in AndroidView without manual FrameLayout wrapping. The factory runs once per composition, which matches the "call once per screen" rule.


Step 5. Delete Preloaded Plans

Delete a specific plan:

val presenter = ExpoFpPlan.preloader.disposePreloadedPlan(preloadedPlanInfo)

Delete all plans:

ExpoFpPlan.preloader.removeAllPreloadedPlans()


Best Practices

  • Preload early: Preload plans during app initialization or screen preparation if you expect repeated access.
  • Reuse presenters: Get presenters from preloaded plans instead of creating new ones to reduce loading time.
  • Clear when not needed: Dispose preloaded plans when they are no longer needed to free memory.
  • Combine with download: Download plans to disk first, then preload them for faster reuse.

Memory Management

Preloaded plans remain in memory until explicitly disposed:

class MainActivity : AppCompatActivity() {
    private var preloadedPlanInfo: ExpoFpPreloadedPlanInfo? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Preload plan once
        if (preloadedPlanInfo == null) {
            preloadedPlanInfo = ExpoFpPlan.preloader.preloadPlan(
                planLink = ExpoFpLinkType.ExpoKey("demo")
            )
        }

        // Get presenter from preloaded plan
        val presenter = ExpoFpPlan.preloader.getPreloadedPlanPresenter(preloadedPlanInfo)

        // Attach to view - can be done multiple times across different Activities
        presenter?.let {
            val planView = it.getView()
            container.addView(planView)
        }
    }

    override fun onDestroy() {
        super.onDestroy()

        // Only dispose when you no longer need the plan
        if (isFinishing) {
            preloadedPlanInfo?.let {
                ExpoFpPlan.preloader.disposePreloadedPlan(it)
            }
        }
    }
}

Reusing Across Multiple Screens

Preloaded plans can be safely reused across different Activities or Fragments:

// Activity A
class MapActivityA : AppCompatActivity() {
    private var presenter: IExpoFpPlanPresenter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        presenter = ExpoFpPlan.preloader.getPreloadedPlanPresenter(preloadedPlanInfo)

        // Each getView() call returns a new host container for this screen
        presenter?.getView()?.let { container.addView(it) }

        // Set message listener for this screen
        presenter?.setMessageListener(myListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Note: the preloaded presenter survives this destroy; no manual cleanup is needed here.
    }
}

// Activity B - reuse the same preloaded plan
class MapActivityB : AppCompatActivity() {
    private var presenter: IExpoFpPlanPresenter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Get the same presenter - no reloading needed!
        presenter = ExpoFpPlan.preloader.getPreloadedPlanPresenter(preloadedPlanInfo)

        // The plan moves to this screen's host once it is attached; when this screen is
        // closed, the plan automatically returns to Activity A
        presenter?.getView()?.let { container.addView(it) }

        // All callbacks still work - set new listener for this screen
        presenter?.setMessageListener(myListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Note: the preloaded presenter survives this destroy; no manual cleanup is needed here.
    }
}

Multiple Activities in Back Stack

When several Activities in the back stack share the same preloaded presenter, the plan is visible on the topmost one. Returning to a previous Activity via the back button restores the plan on it automatically: once the closing Activity is destroyed, the content moves back to the previous screen's host container. No onStart() re-attachment code, FLAG_ACTIVITY_CLEAR_TOP or singleTop launch mode is required for this scenario.

If your code still contains a manual re-attachment pattern from earlier SDK versions (keeping the view reference and re-adding it in onStart() when its parent changed), it is harmless and degrades to a no-op — you can remove it.

Note: Navigation that brings an existing Activity to the front while the previous plan screen stays alive in the back stack (for example reorderToFront) does not move the plan: the content follows attach order. For such flows, obtain a new view with getView() on the screen that should show the plan and replace the previous host in your layout.