Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rnmapbox/maps/llms.txt
Use this file to discover all available pages before exploring further.
The Offline Maps feature allows you to download map regions for offline use, enabling your app to function without network connectivity. The SDK provides a comprehensive API for creating, managing, and monitoring offline map packs.
Overview
Offline maps are managed through the offlineManager singleton, which provides methods for:
- Creating and downloading offline packs
- Monitoring download progress
- Managing existing packs
- Invalidating and updating cached tiles
- Controlling cache size
Basic Setup
Import offlineManager
import { offlineManager } from '@rnmapbox/maps';
Create an offline pack
await offlineManager.createPack(
{
name: 'my-offline-region',
styleURL: 'mapbox://styles/mapbox/streets-v11',
bounds: [[-74.2, 40.7], [-73.8, 41.0]],
minZoom: 10,
maxZoom: 16,
},
progressListener,
errorListener
);
Monitor progress
const progressListener = (pack, status) => {
console.log(`Downloaded: ${status.percentage}%`);
};
const errorListener = (pack, error) => {
console.error('Download error:', error);
};
Creating Offline Packs
Pack Options
Define what region and zoom levels to download:
import { offlineManager, StyleURL } from '@rnmapbox/maps';
const createOfflinePack = async () => {
const packOptions = {
name: 'manhattan-streets',
styleURL: StyleURL.Street,
bounds: [
[-74.0479, 40.6829], // Southwest corner [lng, lat]
[-73.9067, 40.8820], // Northeast corner [lng, lat]
],
minZoom: 10,
maxZoom: 16,
};
await offlineManager.createPack(
packOptions,
onProgress,
onError
);
};
Pack Options Reference
| Option | Type | Required | Description |
|---|
name | string | Yes | Unique identifier for the pack |
styleURL | string | Yes | Map style URL |
bounds | [[lng, lat], [lng, lat]] | Yes | Southwest and northeast corners |
minZoom | number | Yes | Minimum zoom level to download |
maxZoom | number | Yes | Maximum zoom level to download |
The tile count increases exponentially with zoom level. Be cautious when setting high maxZoom values for large areas.
Monitoring Downloads
Progress Listener
Track download progress with detailed status information:
import { useState } from 'react';
import { View, Text, ProgressBar } from 'react-native';
import { offlineManager, OfflinePackDownloadState } from '@rnmapbox/maps';
const OfflineDownloader = () => {
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState('idle');
const onDownloadProgress = (pack, downloadStatus) => {
setProgress(downloadStatus.percentage);
const state = downloadStatus.state;
if (state === OfflinePackDownloadState.Active) {
setStatus('downloading');
} else if (state === OfflinePackDownloadState.Complete) {
setStatus('complete');
} else {
setStatus('inactive');
}
console.log('Progress:', {
percentage: downloadStatus.percentage,
completedTileCount: downloadStatus.completedTileCount,
completedResourceCount: downloadStatus.completedResourceCount,
completedResourceSize: downloadStatus.completedResourceSize,
requiredResourceCount: downloadStatus.requiredResourceCount,
});
};
const onError = (pack, error) => {
console.error('Download failed:', error.message);
setStatus('error');
};
const startDownload = async () => {
await offlineManager.createPack(
{
name: `offline-pack-${Date.now()}`,
styleURL: StyleURL.Street,
bounds: [[-74.2, 40.7], [-73.8, 41.0]],
minZoom: 10,
maxZoom: 14,
},
onDownloadProgress,
onError
);
};
return (
<View>
<Text>Status: {status}</Text>
<ProgressBar progress={progress / 100} />
<Text>Downloaded: {progress.toFixed(1)}%</Text>
</View>
);
};
Progress Status Properties
type OfflineProgressStatus = {
name: string; // Pack name
state: number; // Download state
percentage: number; // Completion percentage (0-100)
completedResourceSize: number; // Bytes downloaded
completedTileCount: number; // Tiles downloaded
completedResourceCount: number; // Resources downloaded
requiredResourceCount: number; // Total resources needed
completedTileSize: number; // Total tile size in bytes
};
Managing Offline Packs
Retrieving Packs
// Get all packs
const allPacks = await offlineManager.getPacks();
console.log(`Found ${allPacks.length} offline packs`);
// Get specific pack by name
const pack = await offlineManager.getPack('manhattan-streets');
if (pack) {
const status = await pack.status();
console.log('Pack status:', status);
}
Pack Methods
const pack = await offlineManager.getPack('my-pack');
// Get pack status
const status = await pack.status();
// Pause download
await pack.pause();
// Resume download
await pack.resume();
Deleting Packs
// Delete a specific pack
await offlineManager.deletePack('manhattan-streets');
// Delete all packs
const packs = await offlineManager.getPacks();
for (const pack of packs) {
await offlineManager.deletePack(pack.name);
}
Invalidating Packs
Update offline tiles without re-downloading everything:
// Invalidate checks if local tiles match server tiles
// Only downloads tiles that have changed
await offlineManager.invalidatePack('manhattan-streets');
invalidatePack is more efficient than deleting and re-downloading. It only fetches tiles that have been updated on the server.
Complete Example
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import {
MapView,
Camera,
offlineManager,
StyleURL,
OfflinePackDownloadState,
} from '@rnmapbox/maps';
const OfflineMapExample = () => {
const [packs, setPacks] = useState([]);
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState(0);
useEffect(() => {
loadExistingPacks();
return () => {
// Cleanup listeners
offlineManager.unsubscribe('my-offline-pack');
};
}, []);
const loadExistingPacks = async () => {
const existingPacks = await offlineManager.getPacks();
setPacks(existingPacks);
};
const downloadRegion = async () => {
setDownloading(true);
setProgress(0);
const packName = `offline-pack-${Date.now()}`;
try {
await offlineManager.createPack(
{
name: packName,
styleURL: StyleURL.Street,
bounds: [[-74.0479, 40.6829], [-73.9067, 40.8820]],
minZoom: 10,
maxZoom: 14,
},
(pack, status) => {
setProgress(status.percentage);
if (status.state === OfflinePackDownloadState.Complete) {
setDownloading(false);
loadExistingPacks();
}
},
(pack, error) => {
console.error('Download error:', error);
setDownloading(false);
}
);
} catch (error) {
console.error('Failed to create pack:', error);
setDownloading(false);
}
};
const deletePack = async (packName) => {
await offlineManager.deletePack(packName);
loadExistingPacks();
};
return (
<View style={styles.container}>
<MapView style={styles.map}>
<Camera
centerCoordinate={[-73.9772, 40.7527]}
zoomLevel={12}
/>
</MapView>
<View style={styles.controls}>
<Button
title={downloading ? `Downloading ${progress.toFixed(0)}%` : 'Download Region'}
onPress={downloadRegion}
disabled={downloading}
/>
<Text style={styles.title}>Offline Packs: {packs.length}</Text>
{packs.map((pack) => (
<View key={pack.name} style={styles.packItem}>
<Text>{pack.name}</Text>
<Button title="Delete" onPress={() => deletePack(pack.name)} />
</View>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
map: { flex: 1 },
controls: {
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
backgroundColor: 'white',
padding: 15,
borderRadius: 10,
},
title: { fontSize: 16, fontWeight: 'bold', marginVertical: 10 },
packItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 5,
},
});
export default OfflineMapExample;
Advanced Features
Setting Tile Count Limit
Limit the maximum number of tiles that can be downloaded:
// Set maximum tiles (Mapbox ToS requirement)
offlineManager.setTileCountLimit(6000);
The Mapbox Terms of Service prohibit changing or bypassing the tile count limit without permission from Mapbox.
Progress Event Throttling
Control how often progress events are emitted:
// Set progress event interval to 500ms (default is 300ms)
offlineManager.setProgressEventThrottle(500);
Subscribing to Existing Packs
Attach listeners to already-created packs:
const pack = await offlineManager.getPack('existing-pack');
// Subscribe to updates
await offlineManager.subscribe(
'existing-pack',
(pack, status) => console.log('Progress:', status.percentage),
(pack, error) => console.error('Error:', error)
);
// Resume download
await pack.resume();
Merging Offline Regions
Sideload offline database from another source:
// Path to offline database file
const dbPath = '/path/to/offline.db';
await offlineManager.mergeOfflineRegions(dbPath);
Reset Database
Delete all offline data and reinitialize:
await offlineManager.resetDatabase();
resetDatabase() permanently deletes all offline packs and cached data. Use with caution.
Cache Management
Set Maximum Cache Size
// Set maximum ambient cache to 50MB
await offlineManager.setMaximumAmbientCacheSize(50 * 1024 * 1024);
// Disable ambient cache
await offlineManager.setMaximumAmbientCacheSize(0);
Calculating Bounds
Use helper libraries to calculate bounds from center coordinates:
import geoViewport from '@mapbox/geo-viewport';
import { Dimensions } from 'react-native';
const calculateBounds = (centerCoordinate, zoom) => {
const { width, height } = Dimensions.get('window');
const bounds = geoViewport.bounds(
centerCoordinate,
zoom,
[width, height],
512 // Tile size
);
return [
[bounds[0], bounds[1]], // Southwest
[bounds[2], bounds[3]], // Northeast
];
};
const bounds = calculateBounds([-73.9772, 40.7527], 12);
Best Practices
- Start with lower zoom levels (10-14) for testing
- Calculate tile counts before downloading large regions
- Provide UI feedback during downloads
- Handle errors gracefully with retry logic
- Clean up listeners in component unmount
- Use
invalidatePack instead of delete + re-download
- Test offline functionality thoroughly
Reference
- Offline Manager:
src/modules/offline/offlineManager.ts:1
- Pack Options:
src/modules/offline/OfflineCreatePackOptions.ts:1
- Offline Pack:
src/modules/offline/OfflinePack.ts:1