react-native-better-maps is a React Native maps library built on Nitro Modules and the New Architecture. It provides a familiar component-based API while leveraging JSI for high-performance native communication.
┌─────────────────────────────────────────────────┐
│ Public API (TypeScript / React) │
│ MapView, Marker, Polyline, Polygon, Circle │
│ Types: Coordinate, Region, Camera, MapViewRef │
├─────────────────────────────────────────────────┤
│ Nitro Layer │
│ MapView.nitro.ts (HybridView spec) │
│ nitro.json (autolinking) │
│ nitrogen/generated/ (codegen output) │
├─────────────────────────────────────────────────┤
│ Native Implementation │
│ HybridMapView host → provider adapter │
│ iOS: AppleMapProviderAdapter → MapKit │
│ GoogleMapProviderAdapter → Google SDK │
│ Android: GoogleMapProviderAdapter → Google SDK │
│ C++: shared geometry / tile logic (optional) │
└─────────────────────────────────────────────────┘
react-native-better-maps/
├── package/ # Library package (react-native-better-maps)
│ ├── src/ # TypeScript source
│ ├── ios/ # Swift native code
│ ├── android/ # Kotlin native code
│ ├── cpp/ # Shared C++ code
│ └── nitro.json # Nitrogen autolinking config
├── example/ # Expo example app
├── docs/ # Documentation
└── .github/ # CI workflows
The root component, backed by a Nitro HybridMapView host. The host owns a stable container view and delegates map behavior to a provider adapter selected before the native SDK map view is created.
Provider defaults are resolved in the React wrapper:
| Platform | Default provider | Current adapter |
|---|---|---|
| iOS | apple |
AppleMapProviderAdapter backed by MapKit |
| Android | google |
GoogleMapProviderAdapter backed by Google Maps SDK |
The iOS host also supports the explicit google provider through GoogleMapProviderAdapter. Unsupported explicit providers fail early in JS. Native hosts also reject unsupported providers if one reaches native code unexpectedly. Changing provider or googleMapId remounts the native view instead of recreating SDK views in place.
Provider adapters own SDK-specific view creation, destruction, lifecycle, camera operations, visible-region calculations, map type, gestures, controls, user location, overlays, press events, clustering, and custom styles. HybridMapView stores Nitro props and callbacks, installs the selected adapter, and replays the current state into that adapter.
Map and overlay callbacks are wired through Nitro listeners on the HybridView. Callbacks receive payloads directly (e.g. onPress(coordinate), onRegionChange(region)).
| Callback | Payload | Notes |
|---|---|---|
onRegionChange / onRegionChangeComplete |
Region |
iOS uses MKCoordinateRegion (center + span); Android derives center + deltas from visible LatLngBounds. Values agree without rotation/pitch but may diverge when the map is tilted or rotated. |
onPress / onLongPress |
Coordinate |
Map background only; marker taps do not also fire map onPress. |
onPoiPress |
PoiPressEvent |
Provider-owned base-map POIs only. Apple Maps emits category data; Google Maps emits place ID. POI taps do not also fire map onPress. |
onMapReady |
none | Fires once after the map finishes loading tiles. |
Marker.onPress / onDragEnd |
none / Coordinate |
Dispatched by overlay id from native to JS registry. |
Overlay onPress |
none | Polyline/polygon/circle with onPress default to tappable on native. |
onClusterPress |
string[], Coordinate |
Fires when a marker cluster is tapped; IDs are member marker overlay ids. |
| Prop / method | Notes |
|---|---|
provider |
Optional map rendering backend. Defaults to apple on iOS and google on Android. Explicit unsupported providers throw. |
googleMapId |
Google Cloud Map ID for the google provider. It is creation-time SDK configuration, so changing it remounts the native map view. |
clusteringEnabled |
Custom grid-based clustering via MarkerClusterEngine on both platforms (viewport-aware, background compute). |
Marker.clusterable |
Opt-out per marker (defaults to true). Non-clusterable markers always render individually. |
markerEnteringAnimation / Marker.enteringAnimation |
Native entering animation for newly added marker render elements. Per-marker values override the map-level default; false is an explicit opt-out. |
clusterEnteringAnimation |
Native entering animation for newly added marker-cluster render elements. Available only on providers with clustering support. |
customMapStyle |
JSON string. The google provider uses Google Maps JSON styles on iOS and Android. The apple provider maps a curated subset to MKMapConfiguration on iOS 16+. |
onPoiPress |
Reports provider-owned points of interest, not app-owned Marker overlays. It is enabled automatically when the callback is present. |
showsUserLocation / followsUserLocation |
Toggles the native user-location layer. Host app must request location permission (NSLocationWhenInUseUsageDescription on iOS; ACCESS_FINE_LOCATION on Android). |
showsCompass / showsScale |
Compass on both platforms. Scale is iOS-only (showsScale is a no-op on Android). |
mapPadding |
Edge insets in density-independent pixels. Applied via layoutMargins (iOS) or setPadding (Android). |
fitToCoordinates(coords, padding?, animated?) |
Imperative ref method; fits camera to a set of coordinates with optional padding. |
- Provider availability —
appleandgoogleare implemented on iOS, andgoogleis implemented on Android.openstreetmapandmapboxare planned provider adapters. - Custom styles on Apple MapKit — no full Google Maps JSON parity; only a curated subset is mapped to MapKit configuration.
- Scale control on Google Maps — Google Maps SDK has no native scale bar;
showsScaleis rejected for thegoogleprovider. - User location — the library toggles the layer only; permission prompts and manifest/Info.plist entries are the host app's responsibility.
followsUserLocationon Android — enables the location layer when permitted; continuous camera follow is not built into Google Maps and may require host-app camera updates.
Marker, Polyline, Polygon, and Circle are overlay components that compose inside MapView. Overlay props are collected on the JS side and serialized into descriptor structs passed to the native HybridMapView (data-driven architecture).
Marker and marker-cluster entering animations follow the same descriptor model. The public API accepts false, system, or a serializable preset config; the React wrapper normalizes that into native descriptors. Native provider adapters execute the animation when a marker render element appears in the render diff. Updating animation config for an already retained marker does not restart the animation; the new config is used the next time that marker is added again.
Google Maps SDKs are sensitive to marker animation churn. Large viewport refreshes can add many native marker instances on the main thread, so the Google provider limits how many markers animate per refresh and reveals the rest immediately. This keeps gestures responsive, but very large marker sets may still need clustering, disabled entering animations, or a future provider-specific animation strategy.
User interaction
↓
React component tree (<MapView><Marker /></MapView>)
↓
MapView collects overlay descriptors + props
↓
Nitro HybridView (JSI, zero-copy structs)
↓
Native HybridMapView (Swift / Kotlin)
↓
Platform map SDK renders
↓
Events flow back via Nitro listeners
↓
React callbacks (onPress, onRegionChange, etc.)
- Source: TypeScript in
package/src/ - Build:
react-native-builder-bob(ESM-only,module+typescripttargets) - Metro: Resolves
sourceexport condition for development - Codegen: Nitrogen reads
*.nitro.tsspecs and generates native bindings
| Decision | Choice | Rationale |
|---|---|---|
| Native bridge | Nitro Modules | JSI-based, type-safe, codegen |
| Architecture | New Architecture only | Required by Nitro Views |
| Build tool | react-native-builder-bob | RN community standard |
| Package manager | Bun workspaces | Fast, modern |
| Module format | ESM-only | Avoids dual-package hazard |
| Example app | Expo SDK 57 | New Arch mandatory, good DX |
| iOS default maps | MapKit | Native, no API key needed |
| Google maps | Google Maps SDK | Shared provider on iOS and Android |
| Provider switching | React remount | Keeps native SDK lifecycle predictable |