Skip to content

Download Plan

Save plans into mobile storage and use them later without internet connection.

Overview

  • Use ExpoFpPlan.downloader to download, unzip, and manage offline plans.
  • A downloaded plan is represented by ExpoFpDownloadedPlanInfo.
  • You can create a presenter from a downloaded plan the same way as from an online plan.
  • Recommended: Use the coroutine-based API. Callback API is available for compatibility (e.g. Java).

Step 1. Download a Plan from the Internet

lifecycleScope.launch {
    val result = ExpoFpPlan.downloader.downloadPlan("demo")
    val info = result.getOrNull()
    Log.i("ExpoTest", "Downloaded (suspend): ${info ?: result.errorOrNull()}")
}

Callback version

ExpoFpPlan.downloader.downloadPlan("demo") { result ->
    Log.i("ExpoTest", "Downloaded (callback): ${result.getOrNull() ?: result.errorOrNull()}")
}

Step 2. Load a Plan from a ZIP Archive

Important: Archive must be named <expoKey>_<version>.zip.
Example: demo_42.zip

val zipFile = File(context.filesDir, "<expoKey>_<version>.zip")

lifecycleScope.launch {
    val result = ExpoFpPlan.downloader.downloadPlanFromZip(zipFile.absolutePath)
    Log.i("ExpoTest", "Unzipped (suspend): ${result.getOrNull() ?: result.errorOrNull()}")
}

Callback version

val zipFile = File(context.filesDir, "<expoKey>_<version>.zip")

ExpoFpPlan.downloader.downloadPlanFromZip(zipFile.absolutePath) { result ->
    Log.i("ExpoTest", "Unzipped (callback): ${result.getOrNull() ?: result.errorOrNull()}")
}

Step 3. Manage Downloaded Plans

Get list of downloaded plans

Coroutine version (recommended):

lifecycleScope.launch {
    val downloadedPlans: List<ExpoFpDownloadedPlanInfo> =
        ExpoFpPlan.downloader.getDownloadedPlansInfo() // or getDownloadedPlansInfo("demo")
}

Callback version:

ExpoFpPlan.downloader.getDownloadedPlansInfo { plans ->
    Log.i("ExpoTest", "Downloaded plans: $plans")
}

Remove old versions

Best practice: Always remove old versions after downloading a new one.

Remove all plans:

lifecycleScope.launch {
    ExpoFpPlan.downloader.removeOldVersionsOfDownloadedPlans()
}

Remove by Expo Key:

lifecycleScope.launch {
    ExpoFpPlan.downloader.removeOldVersionsOfDownloadedPlans("demo")
}

Delete downloaded plans

Remove specific plan:

lifecycleScope.launch {
    ExpoFpPlan.downloader.removeDownloadedPlan(downloadedPlanInfo)
}

Remove all:

lifecycleScope.launch {
    ExpoFpPlan.downloader.removeAllDownloadedPlans()
}


Step 4. Create Presenter from Downloaded Plan

val presenter = ExpoFpPlan.createPlanPresenter(
    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 presenter = ExpoFpPlan.createPlanPresenter(
    planLink = ExpoFpLinkType.DownloadedPlanInfo(downloadedPlanInfo),
    additionalParams = additionalParams,
    locationProvider = locationProvider,
    messageListener = messageListener
)

Step 5. Show Downloaded Plan in UI

View-based UI

val planView = presenter.getView()
yourContainer.addView(planView)

Jetpack Compose

AndroidView(factory = { presenter.getView() })

Technical Notes

How Downloaded Plans Are Loaded

Downloaded plans are stored in app cache storage and loaded via Android's WebViewAssetLoader:

Storage structure:

{app_cache_dir}/expoFpPlan/archives/
  ├── demo_123/
  │   ├── index.html
  │   └── data/
  │       └── data.js
  └── expo2024_456/
      ├── index.html
      └── assets/

Loading mechanism: - All plans load from the same URL: https://localhost/index.html - The WebViewAssetLoader resolves this URL to the correct plan directory - Plan-specific directory is configured in the asset loader's base path

Why this matters: - Plan HTML files use absolute resource paths (e.g., /data/data.js) - This architecture ensures these paths resolve correctly without modifying plan files - Each plan is isolated in its own directory but appears at the same URL to the WebView

This design allows downloaded plans to work identically to online plans from the JavaScript perspective, while being served from local storage.