Loading the Region Catalog

This page explains how to load and refresh the route-network and map-tile region catalogs before users choose required offline regions. Use cached data first, synchronize catalogs when needed, and handle partial catalog results from each subsystem.

Cache and TTL

syncRegions() synchronizes the road-network and map catalogs in parallel and applies the same cache policy to both. The default TTL is 24 hours. Each catalog atomically replaces its own cache, and a failed refresh does not delete the last successful result.

ModeNetwork behaviorRecommended use
AUTOSkips the network when the cache is complete and within its TTL. It refreshes only when missing or expiredScreen entry and automatic background refresh
FORCEAlways refreshes from the network and returns an error when no validated network is availableUser-initiated pull to refresh

fetchRegionLists() reads only the local road-network and map caches without making a network request. To avoid leaving the region screen in a loading state, read and display the cache first, then run AUTO and read the caches again after synchronization. Keep the initial loading state only for a first installation with no cached catalog.

Cache-first Screen Implementation

1
private var catalogReadToken = 0L
2
fun loadRegionCatalog(force: Boolean) {
3
readCachedRegions()
4
val mode = if (force) RegionListSyncMode.FORCE else RegionListSyncMode.AUTO
5
NBNavigation.syncRegions(
6
mode,
7
object : NBNavigationTaskCallback<OfflineRegionCatalogSyncResult> {
8
override fun onSuccess(result: OfflineRegionCatalogSyncResult?) {
9
readCachedRegions()
10
}
11
override fun onError(throwable: Throwable) {
12
readCachedRegions()
13
showCatalogRefreshWarning(throwable)
14
}
15
}
16
)
17
}
18
private fun readCachedRegions() {
19
val token = ++catalogReadToken
20
NBNavigation.fetchRegionLists(
21
"United States",
22
object : NBNavigationTaskCallback<OfflineRegionListResult> {
23
override fun onSuccess(result: OfflineRegionListResult?) {
24
if (token != catalogReadToken || result == null) return
25
showRegions(result.regionRows.filter { it.hasRoute && it.hasMap })
26
result.routeError?.let(::showRouteCatalogWarning)
27
result.mapError?.let(::showMapCatalogWarning)
28
}
29
override fun onError(throwable: Throwable) {
30
if (token == catalogReadToken) {
31
showOfflineError("Loading cached regions failed", throwable)
32
}
33
}
34
}
35
)
36
}

OfflineRegionCatalogSyncResult contains synchronization details for both route and map catalogs. map.networkSkipped is true when AUTO uses a valid cached map catalog. A successful sync does not guarantee that downloadable regions exist. To verify, call fetchRegionLists() after synchronization to read the catalogs.

Country Filtering and Partial Results

The no-argument fetchRegionLists() overload queries United States by default. NBNavigation does not currently expose a public country-enumeration API. Obtain the supported full English country name from the deployment configuration, your product backend, or NextBillion.ai, then pass that exact value to fetchRegionLists(country, callback). Do not pass an ISO code, a localized name, or a whitespace-only string. Treat the country as product configuration rather than deriving it from the device locale.

OfflineRegionListResult may contain data from only the road-network or map side. Check routeError, mapError, hasRouteData, hasMapData, and regionRows together. Do not interpret an empty list after filtering on hasRoute and hasMap as proof that no data exists, because the screen can display whichever cache remains available.

Reading installed map regions on a cold start may access the disk. Use the asynchronous listInstalledMapTileRegions() API instead of reading Maps SDK storage directly on the main thread.

Region List Fields, Data Size, and Update Detection

After fetchRegionLists() succeeds, use result.regionRows as the primary screen model. It aligns route and map catalogs by regionId and preserves regions available from only one source. Filter on hasRoute && hasMap only when the product explicitly requires a complete offline-navigation package.

Result object OfflineRegionListResult

FieldMeaningRecommended use
routeRegionListLocally cached route catalog; null when unavailable or unreadableUse for catalog metadata or grouped route queries
mapRegionListLocally cached map catalog; null when unavailable or unreadableUse for catalog metadata or map diagnostics
routeError / mapErrorSource-specific error or stale-cache warningThe other source and regionRows may still be usable
regionRowsRoute and map entries aligned by regionIdPreferred source for a regional-list UI
updatedAtMsWall-clock time when this combined result was constructedThis is not the last catalog-sync time
hasRouteDataWhether routeRegionList existsDoes not mean every row contains route data
hasMapDataWhether mapRegionList existsDoes not mean every row contains map data

Read routeRegionList.listSyncedAtMs and mapRegionList.listSyncedAtMs for catalog freshness; updatedAtMs only timestamps this response. mapRegionList.listSyncStatus can be empty, ok, or failed. Do not infer synchronization success from total or an empty list alone without simultaneously inspecting routeError and mapError.

List item OfflineRegionListRow

FieldMeaningRecommended use
regionIdStable ID shared by the route and map catalogsUse for download, pause, resume, cancel, delete, and version association
routeRegionOfflineRegionLeaf for this ID. It will be “null” when absent from the route catalogRead route version, download state, progress, and bounds
mapRegionMapTileRegionListItem for this ID. It will be “null” when absent from the map catalogRead map version, estimated size, and tile count
displayNameRoute adminL3, then map name, then Region #idSuitable as the default UI title
countryRoute country, then map adminL0Can be “null”. Do not query with a localized replacement
adminL1Route adminL1, then map adminL1May be “null”
adminL2Route-catalog level-2 administrative nameThe map catalog does not currently supply it. Can be “null”
adminL3Route adminL3, then map adminL3Can be “null”
routeSizeBytesEstimated compressed route download size in bytes“null” means the route side is absent but it does not mean zero size.
mapSizeBytesEstimated compressed map download size in bytes“null” means the map side is absent but it does not mean zero size.
hasRouterouteRegion != nullWhether the row can provide a route package
hasMapmapRegion != nullWhether the row can provide a map package

Route item routeRegion and OfflineRegionLeaf

FieldMeaningRecommended use
regionIdRoute-region IDShould equal the outer row.regionId
country / adminL1 / adminL2 / adminL3Catalog administrative hierarchyadminL3 is normally displayed; values can be empty strings
versionCurrent server route-catalog versionNormally consumed by the SDK updateAvailable calculation
timestampSecCatalog-version time in Unix epoch secondsUsed for version comparison; dataVersionLabel is available for display
totalSizeBytesEstimated compressed route-package sizeUse for pre-download display and storage estimates
downloadStatusCan be one of IDLE, DOWNLOADING, COMPLETE, PARTIAL, FAILED, PAUSED, or UNKNOWNPrefer isDownloaded, isPartial, or unified progress for business decisions
tilesTotal / tilesDonePlanned and completed route-tile countsCalculate detailed progress only when the total is valid
detailSyncStatusRaw region-detail or manifest synchronization stateUse for diagnostics, not as the only readiness gate
catalogRemovedThe local entry is absent from the latest server catalogLocal data remains, so do not treat it as an update
catalogUpdateAvailableSDK-computed route-catalog update flagPrefer the equivalent updateAvailable property
boundaryBboxWGS84 bounds derived from cached boundary GeoJSONUse for viewport intersection when detail is present
catalogBboxWGS84 bounds supplied directly by the catalogUse for coarse filtering before boundary detail is downloaded
displayNameDerived from adminL3, adminL2, or regionIdThe outer row.displayName also considers the map name
sizeWithUnit`Human-readable totalSizeBytesUseful when displaying only the route-package size
isDownloaded / `isPartialDerived from status and tilesDone/ tilesTotalUse for route-side availability and resume controls
updateAvailablePublic alias of catalogUpdateAvailableAfter catalog sync, use it to show a route-update prompt
dataVersionLabeltimestampSec formatted as yyyy-MM-ddDisplay only. Do not parse it back for version comparison

Map item mapRegion and MapTileRegionListItem

FieldMeaningRecommended use
regionIdMap-region IDShould equal the outer row.regionId
nameMap-region name from the server catalogUsed by row.displayName when the route name is absent
adminL0English country or level-0 administrative nameUsed when filtering the map catalog by country
adminL1Level-1 administrative nameCan be used for grouped display
adminL3Level-3 administrative nameCan be used for finer display. adminL2 is currently not supplied
versionServer map-region version. It can be nullCompare with the installed regionVersion
totalSizeBytesEstimated compressed map-package sizeUse for pre-download display and storage estimates
tileCountServer-provided estimated tile countAn estimate, not a package-count limit
timestampRaw server timestamp. It can be null and is preserved without unit conversionDo not assume it to be in seconds. Prefer version for map-update comparison

Calculate download size and available storage

For a first download, add non-null routeSizeBytes and mapSizeBytes to obtain the combined estimated compressed download size. This excludes extraction, database pages, temporary files, and filesystem overhead, so it is not the final on-disk size.

For a resumed download, when downloaded and total are available from observeOfflineProgress(), sum max(total - downloaded, 0) for both subtasks or else fall back to the full catalog estimate.

1
private fun estimatedBytes(row: OfflineRegionListRow): Long {
2
val route = (row.routeSizeBytes ?: 0L).coerceAtLeast(0L)
3
val map = (row.mapSizeBytes ?: 0L).coerceAtLeast(0L)
4
return if (route > Long.MAX_VALUE - map) Long.MAX_VALUE else route + map
5
}
6
7
val text = Formatter.formatFileSize(context, estimatedBytes(row))

Recommended preflight: available bytes >= remaining download bytes + max(20% of remaining bytes, 100 MB). NBNavigation does not currently expose one combined storage-preflight result for route and map downloads, so the application should still use StatFs on the volume that contains the actual data directory. Check each volume separately if route and map data use different volumes. Insufficient storage can still surface through progress or callbacks as FAILED, FAILED_STORAGE, or a structured error.

1
val remaining = remainingBytes.coerceAtLeast(0L)
2
val reserve = maxOf(
3
100L * 1024L * 1024L,
4
(remaining * 0.20).roundToLong()
5
)
6
val required = if (remaining > Long.MAX_VALUE - reserve) Long.MAX_VALUE
7
else remaining + reserve
8
val enoughSpace = StatFs(storageDir.absolutePath).availableBytes >= required