Offline Maps
expo-gaode-map supports offline map functionality, allowing users to download city map data for use in offline environments.
Important Note
Offline maps require the 3D Map SDK. The 2D SDK does not support offline maps.
Android Permission Requirements
Offline maps require the following permissions (Config Plugin adds them automatically):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />If using Config Plugin, these permissions are configured automatically. For manual setup, ensure these permissions are added to your AndroidManifest.xml.
Features
- ✅ City Map Downloads - Download offline map data for cities across China
- ✅ Download Management - Pause, resume, and cancel downloads
- ✅ Real-time Progress - Monitor download progress and status in real-time
- ✅ Storage Management - View storage usage, delete and clear maps
- ✅ Auto Updates - Check and update offline map data
- ✅ Network Control - Restrict downloads to WiFi only
Quick Start
1. Get Available Cities
import { ExpoGaodeMapOfflineModule } from 'expo-gaode-map';
const cities = await ExpoGaodeMapOfflineModule.getAvailableCities();
cities.forEach(city => {
console.log(`${city.cityName}: ${city.size / 1024 / 1024} MB`);
});2. Start Download
// Download Beijing map (WiFi only)
await ExpoGaodeMapOfflineModule.startDownload({
cityCode: '110000',
allowCellular: false, // WiFi only
});3. Listen to Download Progress
import { useEffect } from 'react';
useEffect(() => {
// Listen to download progress
const progressSub = ExpoGaodeMapOfflineModule.addDownloadProgressListener((event) => {
console.log(`${event.cityName}: ${event.progress}%`);
});
// Listen to download completion
const completeSub = ExpoGaodeMapOfflineModule.addDownloadCompleteListener((event) => {
console.log(`${event.cityName} download complete!`);
});
// Listen to download errors
const errorSub = ExpoGaodeMapOfflineModule.addDownloadErrorListener((event) => {
console.error(`${event.cityName} download failed: ${event.error}`);
});
// Cleanup listeners
return () => {
progressSub.remove();
completeSub.remove();
errorSub.remove();
};
}, []);Complete Example
Here's a complete example with download management, progress display, and status updates:
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, TouchableOpacity, Alert } from 'react-native';
import { ExpoGaodeMapOfflineModule, type OfflineMapInfo } from 'expo-gaode-map';
export default function OfflineMapScreen() {
const [cities, setCities] = useState<OfflineMapInfo[]>([]);
const [downloadedCities, setDownloadedCities] = useState<string[]>([]);
const [progress, setProgress] = useState<Record<string, number>>({});
const [downloading, setDownloading] = useState<string | null>(null);
// Load city list
useEffect(() => {
loadCities();
}, []);
const loadCities = async () => {
const [available, downloaded] = await Promise.all([
ExpoGaodeMapOfflineModule.getAvailableCities(),
ExpoGaodeMapOfflineModule.getDownloadedMaps(),
]);
setCities(available.slice(0, 20)); // Show first 20 cities
setDownloadedCities(downloaded.map(c => c.cityCode));
};
// Listen to download events
useEffect(() => {
const progressSub = ExpoGaodeMapOfflineModule.addDownloadProgressListener((event) => {
setProgress(prev => ({
...prev,
[event.cityCode]: event.progress,
}));
});
const completeSub = ExpoGaodeMapOfflineModule.addDownloadCompleteListener((event) => {
Alert.alert('Download Complete', `${event.cityName} offline map downloaded`);
setDownloading(null);
loadCities();
});
const errorSub = ExpoGaodeMapOfflineModule.addDownloadErrorListener((event) => {
Alert.alert('Download Failed', `${event.cityName}: ${event.error}`);
setDownloading(null);
});
return () => {
progressSub.remove();
completeSub.remove();
errorSub.remove();
};
}, []);
// Start download
const handleDownload = async (city: OfflineMapInfo) => {
setDownloading(city.cityCode);
try {
await ExpoGaodeMapOfflineModule.startDownload({
cityCode: city.cityCode,
allowCellular: false, // WiFi only
});
} catch (error) {
console.error('Download failed:', error);
Alert.alert('Error', 'Failed to start download');
setDownloading(null);
}
};
// Pause download
const handlePause = async (cityCode: string) => {
try {
await ExpoGaodeMapOfflineModule.pauseDownload(cityCode);
setDownloading(null);
} catch (error) {
console.error('Pause failed:', error);
}
};
// Delete map
const handleDelete = async (city: OfflineMapInfo) => {
Alert.alert(
'Confirm Delete',
`Delete offline map for ${city.cityName}?`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
style: 'destructive',
onPress: async () => {
await ExpoGaodeMapOfflineModule.deleteMap(city.cityCode);
loadCities();
},
},
]
);
};
// Render city item
const renderCity = ({ item }: { item: OfflineMapInfo }) => {
const isDownloaded = downloadedCities.includes(item.cityCode);
const isDownloading = downloading === item.cityCode;
const currentProgress = progress[item.cityCode] || 0;
return (
<View style={{ padding: 16, borderBottomWidth: 1, borderBottomColor: '#eee' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>{item.cityName}</Text>
<Text style={{ color: '#666', marginTop: 4 }}>
{(item.size / 1024 / 1024).toFixed(1)} MB
</Text>
{isDownloading && (
<View style={{ marginTop: 8 }}>
<View style={{ height: 8, backgroundColor: '#eee', borderRadius: 4 }}>
<View
style={{
height: '100%',
width: `${currentProgress}%`,
backgroundColor: '#4CAF50',
borderRadius: 4,
}}
/>
</View>
<Text style={{ marginTop: 4, color: '#666' }}>{currentProgress}%</Text>
</View>
)}
<View style={{ flexDirection: 'row', marginTop: 8 }}>
{!isDownloaded && !isDownloading && (
<TouchableOpacity
onPress={() => handleDownload(item)}
style={{
backgroundColor: '#2196F3',
padding: 8,
borderRadius: 4,
}}
>
<Text style={{ color: 'white' }}>Download</Text>
</TouchableOpacity>
)}
{isDownloading && (
<TouchableOpacity
onPress={() => handlePause(item.cityCode)}
style={{
backgroundColor: '#FF9800',
padding: 8,
borderRadius: 4,
}}
>
<Text style={{ color: 'white' }}>Pause</Text>
</TouchableOpacity>
)}
{isDownloaded && (
<TouchableOpacity
onPress={() => handleDelete(item)}
style={{
backgroundColor: '#F44336',
padding: 8,
borderRadius: 4,
}}
>
<Text style={{ color: 'white' }}>Delete</Text>
</TouchableOpacity>
)}
</View>
</View>
);
};
return (
<View style={{ flex: 1 }}>
<View style={{ padding: 16, backgroundColor: '#f5f5f5' }}>
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>Offline Map Manager</Text>
<Text style={{ marginTop: 4, color: '#666' }}>
Downloaded: {downloadedCities.length} cities
</Text>
</View>
<FlatList
data={cities}
keyExtractor={item => item.cityCode}
renderItem={renderCity}
/>
</View>
);
}API Reference
ExpoGaodeMapOfflineModule
Methods
getAvailableCities()
Get list of all downloadable cities.
const cities = await ExpoGaodeMapOfflineModule.getAvailableCities();Returns: Promise<OfflineMapInfo[]>
startDownload(options)
Start downloading offline map.
await ExpoGaodeMapOfflineModule.startDownload({
cityCode: '110000',
allowCellular: false,
});Parameters:
cityCode: City codeallowCellular: Allow downloads over cellular network (defaultfalse)
pauseDownload(cityCode)
Pause download.
await ExpoGaodeMapOfflineModule.pauseDownload('110000');resumeDownload(cityCode)
Resume download.
await ExpoGaodeMapOfflineModule.resumeDownload('110000');deleteMap(cityCode)
Delete offline map.
await ExpoGaodeMapOfflineModule.deleteMap('110000');clearAll()
Clear all offline maps.
await ExpoGaodeMapOfflineModule.clearAll();getDownloadedMaps()
Get list of downloaded maps.
const downloaded = await ExpoGaodeMapOfflineModule.getDownloadedMaps();Returns: Promise<OfflineMapInfo[]>
getStorageInfo()
Get storage information.
const storage = await ExpoGaodeMapOfflineModule.getStorageInfo();
console.log('Used:', storage.usedSpace / 1024 / 1024, 'MB');
console.log('Available:', storage.availableSpace / 1024 / 1024, 'MB');checkUpdate(cityCode)
Check if map has updates.
const hasUpdate = await ExpoGaodeMapOfflineModule.checkUpdate('110000');
if (hasUpdate) {
await ExpoGaodeMapOfflineModule.updateMap('110000');
}updateMap(cityCode)
Update offline map.
await ExpoGaodeMapOfflineModule.updateMap('110000');Event Listeners
addDownloadProgressListener(callback)
Listen to download progress.
const subscription = ExpoGaodeMapOfflineModule.addDownloadProgressListener((event) => {
console.log(`${event.cityName}: ${event.progress}%`);
});
// Cleanup
subscription.remove();Event object:
{
cityCode: string;
cityName: string;
progress: number; // 0-100
}addDownloadCompleteListener(callback)
Listen to download completion.
const subscription = ExpoGaodeMapOfflineModule.addDownloadCompleteListener((event) => {
console.log(`${event.cityName} download complete`);
});addDownloadErrorListener(callback)
Listen to download errors.
const subscription = ExpoGaodeMapOfflineModule.addDownloadErrorListener((event) => {
console.error(`${event.cityName} error: ${event.error}`);
});addDownloadPausedListener(callback)
Listen to download pauses.
const subscription = ExpoGaodeMapOfflineModule.addDownloadPausedListener((event) => {
console.log(`${event.cityName} paused`);
});addDownloadCancelledListener(callback)
Listen to download cancellations.
const subscription = ExpoGaodeMapOfflineModule.addDownloadCancelledListener((event) => {
console.log(`${event.cityName} cancelled`);
});Type Definitions
interface OfflineMapInfo {
cityCode: string; // City code
cityName: string; // City name
size: number; // Map size (bytes)
status: OfflineMapStatus;
}
type OfflineMapStatus =
| 'NOT_DOWNLOADED' // Not downloaded
| 'DOWNLOADING' // Downloading
| 'PAUSED' // Paused
| 'DOWNLOADED' // Downloaded
| 'ERROR'; // Error
interface StorageInfo {
usedSpace: number; // Used space (bytes)
availableSpace: number; // Available space (bytes)
totalSpace: number; // Total space (bytes)
}Best Practices
1. Check Storage Space
Check available space before downloading:
const handleDownload = async (city: OfflineMapInfo) => {
const storage = await ExpoGaodeMapOfflineModule.getStorageInfo();
if (storage.availableSpace < city.size) {
Alert.alert('Insufficient Storage', 'Please free up storage space');
return;
}
await ExpoGaodeMapOfflineModule.startDownload({
cityCode: city.cityCode,
allowCellular: false,
});
};2. Error Handling
Include error handling for all async operations:
try {
await ExpoGaodeMapOfflineModule.startDownload({ cityCode });
} catch (error) {
console.error('Download failed:', error);
Alert.alert('Error', 'Failed to start download, please try again');
}3. Cleanup Listeners
Use useEffect cleanup function:
useEffect(() => {
const progressSub = ExpoGaodeMapOfflineModule.addDownloadProgressListener(handler);
return () => {
progressSub.remove(); // Cleanup listener
};
}, []);4. Network Control
Default to WiFi-only downloads to protect user's data:
await ExpoGaodeMapOfflineModule.startDownload({
cityCode: city.cityCode,
allowCellular: false, // ✅ Recommended
});5. Auto Update Check
Periodically check for map updates:
const checkUpdates = async () => {
const downloaded = await ExpoGaodeMapOfflineModule.getDownloadedMaps();
for (const city of downloaded) {
const hasUpdate = await ExpoGaodeMapOfflineModule.checkUpdate(city.cityCode);
if (hasUpdate) {
// Notify user about update
}
}
};Important Notes
- SDK Requirement: Offline maps only work with the 3D Map SDK
- Android Permissions: Requires network and storage permissions (Config Plugin adds automatically):
INTERNET- Download map dataACCESS_NETWORK_STATE- Check network statusWRITE_EXTERNAL_STORAGE- Store offline maps
- Storage Space: Each city map typically requires 50-150 MB
- Download Time: Depends on network speed and map size
- Auto Loading: SDK automatically uses offline data after download
- Data Updates: Regularly check and update offline maps
FAQ
Where are offline maps stored?
Offline map data is stored in the app's private directory and will be deleted when the app is uninstalled.
How do I know the map is using offline data?
The SDK automatically detects and uses offline data without extra configuration. In offline mode, the map automatically switches to offline data.
Can I download multiple cities simultaneously?
Technically yes, but it's recommended to download one city at a time for better progress management and error handling.
How to resume after download interruption?
Use the resumeDownload() method to resume from where it stopped:
await ExpoGaodeMapOfflineModule.resumeDownload(cityCode);How to delete all offline maps?
Use the clearAll() method:
await ExpoGaodeMapOfflineModule.clearAll();