A small, runnable Expo app showing how to build a live Mapbox map with a route line, stop pins, and a "you are here" driver dot in React Native — plus a second screen for a geofenced arrival check (is the driver inside a radius around a target location?).
This is a cleaned-up extract of a real feature built for Sarvika's Supply Chain Management mobile app (last-mile delivery tracking). The business logic (facility names, delivery workflow, backend wiring) has been stripped out; the Mapbox mechanics — the actually reusable, non-obvious part — are kept intact. Use this as a starting point whenever a project needs "a live map with a route and a moving location marker" instead of re-solving the same problems from scratch.
| File | Demonstrates |
|---|---|
src/components/LiveRouteMap.tsx |
Full route + "active leg" polylines with a gradient/glow style, stop pins that declutter when close together, direction-arrow chips, camera auto-fit, a live driver position |
src/components/GeofencedMapPreview.tsx |
A radius-based geofence rendered as a real GeoJSON circle (Mapbox has no meters-based circle primitive), with in-range/out-of-range styling |
src/components/CurrentLocationDot.tsx |
A reusable pulsing "you are here" marker |
src/utils/polyline6.ts |
Decoding a polyline6-encoded route geometry (the format Mapbox Directions / most routing APIs return) into coordinates |
src/utils/geo.ts |
Haversine distance + generating a real-world-meters circle polygon for the geofence |
metro.config.js |
Two Metro gotchas specific to @rnmapbox/maps (see below) |
Run it, then use src/screens/RouteMapDemoScreen.tsx and
src/screens/GeofenceDemoScreen.tsx as the "how do I wire this up" reference.
- A Mapbox account and a public access token
(starts with
pk.) — free tier is enough for this demo. @rnmapbox/mapsis a native module. This is not an Expo Go app — Expo Go doesn't include Mapbox's native code. You need a custom dev client (already wired up here via theexpo-dev-clientplugin) or a fullexpo run:android/expo run:iosbuild.- Depending on your Mapbox SDK version and platform, you may also need a
Mapbox secret downloads token (different from the public access
token) configured for the native Android/iOS build to even fetch the SDK.
This wasn't required by the source app's current setup, but check
@rnmapbox/maps' own install docs for your exact version if the native build fails to pull dependencies.
npm install
cp .env.example .env
# edit .env and paste in your Mapbox public access tokenThen either:
npx expo run:android # builds a real dev client + installs it
npx expo run:iosor, if you already have a dev client build installed on a device/simulator:
npx expo startnpx expo start --web also works, but only for layout — see the web
fallback note below.
- Android: a Mapbox
MapViewinside aScrollViewfights the outer scroll gesture for touches.react-native-gesture-handler'sGesture.Native()does not resolve this for@rnmapbox/maps'sMapView.LiveRouteMapexposesonTouchStart/onTouchEndso the parent screen can disable its ownScrollViewwhile a finger is on the map — seeRouteMapDemoScreen.tsxfor the wiring. Camera.boundssilently no-ops if you fit bounds before the nativeMapViewhas taken on its real layout size (fails with"map size is {24, 24}"on-device, with no retry). Both map components gate the first<Camera>fit ononDidFinishLoadingMap, which only fires after layout has already happened — simpler than a manualonLayoutsize check.- No real-world-meters circle primitive.
CircleLayer's radius is in screen pixels, not meters, so it zooms wrong.geo.ts'scirclePolygon()generates an actual GeoJSON polygon ring instead, fed throughShapeSource+FillLayer/LineLayer. @rnmapbox/mapshas no web build worth using. Its web bundle doesn't exportShapeSource/LineLayer/PointAnnotationat all, and pulls in amapbox-glCSS file Metro can't load. Both map components render a static text fallback onPlatform.OS === 'web'instead of crashing, andmetro.config.jsstubs out themapbox-gl/.cssimports so the web bundle can even compile in the first place.- Stop pins that sit too close together get pushed apart (scaled to
how zoomed-out the current route is, not a fixed distance), and
direction-arrow chips are placed one per route leg, at that leg's own
midpoint, ranked by real length and capped at a handful so they land on
the stretches that actually benefit from one rather than clustering near
the start — both in
LiveRouteMap.tsx.
- Navigation between the two demo screens is a plain
useStatetoggle inApp.tsx, not a routing library — add@react-navigationyourself if your project needs real navigation. - No backend/routing-API integration.
src/mock/sampleRoute.tsbuilds a fake route with a tiny localencodePolyline6()helper just so the demo exercises the same decode path production data would go through — a real app would get this string from Mapbox Directions or its own routing service instead.