Skip to main content

Getting Started

Complete guide for integrating Barikoi Maps into an existing React Native (Expo) application.

Prerequisites

RequirementVersion tested
Node.js≥ 18 LTS
Expo CLInpx expo (bundled with expo package)
EAS CLIeas-cli ≥ 20.4.0 (for device / cloud builds)
Expo SDK54 (expo ~54.0.34)
React Native0.81.x (ships with Expo SDK 54)
Xcode16+ (iOS builds)
Android StudioHedgehog+ with NDK (Android builds)
Barikoi API keyFree tier available — register here

Expo Go will not work. The MapLibre native module requires a custom dev client. You must use expo prebuild + expo run:android/ios or EAS Build.


Installation

1. Get your API key

  1. Register at the Barikoi Developer Dashboard.
  2. Verify with your phone number.
  3. Claim your API key (starts with bkoi_…).

Keep the key handy — you will reference it in your map style URL and API client.


2. Install dependencies

npx expo install \
@maplibre/maplibre-react-native@^11.3.6 \
expo-location@~19.0.8 \
expo-dev-client@~6.0.21

npm install barikoiapis@^2.0.2

If you are using TypeScript, also install the GeoJSON type definitions:

npm install -D @types/geojson
PackagePurpose
@maplibre/maplibre-react-nativeNative MapLibre GL map view, camera, markers
expo-locationForeground/background location permissions & GPS
expo-dev-clientCustom development client (required for native modules)
barikoiapisOfficial Barikoi TypeScript SDK — autocomplete, reverse geocode, routing, etc.

Why npx expo install? It pins versions compatible with your current Expo SDK. Use plain npm install for packages that aren't in the Expo version policy (e.g. barikoiapis).

Why expo-dev-client?

Barikoi Maps uses native modules that cannot run in Expo Go. expo-dev-client lets you create a development build that supports native code.


3. Update app.json

Add the MapLibre Expo config plugin to the plugins array. This replaces all manual build.gradle / Podfile edits that are necessary in bare React Native. Add these entries inside your existing expo object — do not replace your entire app.json:

{
"expo": {
"name": "mapapp",
"slug": "mapapp",
"version": "1.0.0",
"newArchEnabled": true,
"plugins": [
"expo-router",
"@maplibre/maplibre-react-native", // ← required for native map module
[
"expo-splash-screen",
{ /* …your splash config… */ }
]
],
"extra": {
"router": {},
"eas": {
"projectId": "<YOUR_EAS_PROJECT_ID>"
}
}
}
}

Key differences from bare React Native:

Bare RNExpo
Manually edit android/build.gradle, settings.gradle, MainApplicationJust add "@maplibre/maplibre-react-native" to plugins
Manually edit ios/Podfile and run pod installPlugin handles CocoaPods config during expo prebuild
Manually link native modulesConfig plugins auto-link during prebuild
note

The @maplibre/maplibre-react-native plugin entry is required — without it the native map module will not register correctly.

New Architecture

If you want to enable the New Architecture, add "newArchEnabled": true to your expo object. Be aware this may affect other native modules in your project. Test thoroughly before enabling it in an existing project.


4. Create the map utilities file

Create utils/mapUtils.ts in your project. This file centralises Barikoi-specific defaults so every screen stays consistent. Adjust the import path in your screen files based on where you place this file.

// utils/mapUtils.ts

/**
* Map style URLs — append your API key as a query parameter.
* Barikoi offers several vector-tile styles; osm-liberty is the default.
*/
export const MAP_STYLES = {
osmLiberty: (apiKey: string) =>
`https://map.barikoi.com/styles/osm-liberty/style.json?key=${apiKey}`,
// Add other Barikoi styles here as they become available
} as const;

/**
* Default centre point: Dhaka, Bangladesh.
* Coordinates are in GeoJSON order: [longitude, latitude].
*/
export const DEFAULT_CENTER: [number, number] = [90.4125, 23.8103];

/**
* Sensible zoom defaults.
*/
export const DEFAULT_ZOOM = 12;
export const SELECTED_ZOOM = 16;
export const USER_LOCATION_ZOOM = 14;
export const CLOSE_ZOOM = 15;

/**
* Camera animation defaults (milliseconds).
*/
export const FLY_DURATION_MS = 1000;

/**
* Brand / accent colour used throughout the UI.
*/
export const BRAND_BLUE = '#007AFF';

// ── Helper functions ────────────────────────────────────────────────

/**
* Parse a string-or-number value into a finite float.
* Returns `NaN` for null / undefined / non-numeric strings.
*/
export function safeParseFloat(value: string | number | undefined): number {
return parseFloat(String(value));
}

/**
* Return true when both lon and lat are finite numbers inside valid ranges.
*/
export function isValidCoordinate(lon: number, lat: number): boolean {
return (
Number.isFinite(lon) &&
Number.isFinite(lat) &&
lon >= -180 &&
lon <= 180 &&
lat >= -90 &&
lat <= 90
);
}

/**
* Haversine distance between two [lon, lat] points in metres.
*/
export function haversineDistance(
a: [number, number],
b: [number, number],
): number {
const toRad = (deg: number) => (deg * Math.PI) / 180;
const R = 6_371_000; // Earth radius in metres
const dLat = toRad(b[1] - a[1]);
const dLon = toRad(b[0] - a[0]);
const sinLat = Math.sin(dLat / 2);
const sinLon = Math.sin(dLon / 2);
const h =
sinLat * sinLat +
Math.cos(toRad(a[1])) * Math.cos(toRad(b[1])) * sinLon * sinLon;
return R * 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
}

/**
* Basic point-in-polygon (ray-casting) for [lon, lat] arrays.
*/
export function pointInPolygon(
point: [number, number],
polygon: [number, number][],
): boolean {
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i][0], yi = polygon[i][1];
const xj = polygon[j][0], yj = polygon[j][1];
const intersect =
yi > point[1] !== yj > point[1] &&
point[0] < ((xj - xi) * (point[1] - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}

5. Build and run

Because @maplibre/maplibre-react-native includes native code, you must rebuild the native projects whenever you add or change config plugins.

Rebuild required

Any time you change the plugins array in app.json, or change newArchEnabled, you must create a new native build. A Metro reload or hot reload is not enough.

Local development (physical device or emulator)

# Generate native projects (runs the config plugins)
npx expo prebuild

# Run on Android
npx expo run:android

# Run on iOS
npx expo run:ios

After the first build, start the JS bundler with:

npx expo start

Cloud builds with EAS

# Install EAS CLI (one-time)
npm install -g eas-cli

# Build a development client
eas build --profile development --platform android
eas build --profile development --platform ios

Your eas.json should be configured with the required profiles:

{
"cli": {
"version": ">= 20.4.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

Coordinate convention

Barikoi tiles and the MapLibre SDK use GeoJSON order: [longitude, latitude].

// ✅ Correct — GeoJSON order
const dhaka: [number, number] = [90.4125, 23.8103];

// ❌ Wrong — Google Maps order (lat, lng)
const wrong: [number, number] = [23.8103, 90.4125];

The expo-location API returns { coords: { latitude, longitude } }, so when converting to a coordinate tuple make sure you swap the order:

const loc = await Location.getCurrentPositionAsync();
const coords: [number, number] = [loc.coords.longitude, loc.coords.latitude];
// ^^^^^^^^^ first ^^^^^^^^ second

Simple map

A minimal screen that renders Barikoi map tiles and centres on the user's location. Copy this into any route file (e.g. app/(tabs)/map.tsx).

API key

The example below stores the API key in component state for runtime configurability. You can alternatively store it under expo.extra.barikoiApiKey in app.json and read it with expo-constantsConstants.expoConfig?.extra?.barikoiApiKey.

import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text, ActivityIndicator, Platform } from 'react-native';
import { Map, Camera, Marker } from '@maplibre/maplibre-react-native';
import * as Location from 'expo-location';

// ── Configuration ───────────────────────────────────────────────────
const BARIKOI_API_KEY = 'YOUR_BARIKOI_API_KEY'; // replace with your key
const MAP_STYLE_URL = `https://map.barikoi.com/styles/osm-liberty/style.json?key=${BARIKOI_API_KEY}`;

const DEFAULT_CENTER: [number, number] = [90.4125, 23.8103]; // Dhaka
const DEFAULT_ZOOM = 12;

// ── Component ───────────────────────────────────────────────────────
export default function SimpleMapScreen() {
const [center, setCenter] = useState<[number, number]>(DEFAULT_CENTER);
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
(async () => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setError('Location permission denied');
setLoading(false);
return;
}

const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});

setCenter([location.coords.longitude, location.coords.latitude]);
setZoom(14);
} catch (err) {
console.warn('Could not get location:', err);
} finally {
setLoading(false);
}
})();
}, []);

if (loading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Loading map…</Text>
</View>
);
}

return (
<View style={styles.container}>
{error && (
<View style={styles.errorBanner}>
<Text style={styles.errorText}>{error}</Text>
</View>
)}

<Map
style={styles.map}
mapStyle={MAP_STYLE_URL}
logo={false}
attribution={false}
>
<Camera
zoom={zoom}
center={center}
duration={1000}
easing="fly"
/>

<Marker id="center-marker" lngLat={center}>
<View style={styles.marker} />
</Marker>
</Map>
</View>
);
}

// ── Styles ──────────────────────────────────────────────────────────
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
...StyleSheet.absoluteFillObject,
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 12,
fontSize: 14,
color: '#64748B',
},
errorBanner: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 36,
left: 16,
right: 16,
zIndex: 10,
backgroundColor: 'rgba(239, 68, 68, 0.95)',
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 10,
},
errorText: {
color: '#FFFFFF',
fontSize: 13,
textAlign: 'center',
fontWeight: '500',
},
marker: {
width: 16,
height: 16,
borderRadius: 8,
backgroundColor: '#007AFF',
borderWidth: 2,
borderColor: '#FFFFFF',
},
});

Utilities reference

Exports from the suggested utils/mapUtils.ts file (see § 4):

ExportTypeDescription
MAP_STYLESRecord<string, (apiKey: string) => string>Functions that return Barikoi style URLs with the API key appended
DEFAULT_CENTER[number, number]Default map centre — Dhaka [90.4125, 23.8103]
DEFAULT_ZOOMnumberInitial zoom level (12)
SELECTED_ZOOMnumberZoom when a search result is selected (16)
USER_LOCATION_ZOOMnumberZoom when centring on user GPS (14)
CLOSE_ZOOMnumberZoom for "centre-on-me" button (15)
FLY_DURATION_MSnumberCamera fly-to animation duration (1000 ms)
BRAND_BLUEstringAccent colour for markers and buttons (#007AFF)
safeParseFloat(value)(string | number | undefined) => numberParse a value to float, returning NaN on bad input
isValidCoordinate(lon, lat)(number, number) => booleanValidates lon/lat are finite and within WGS-84 ranges
haversineDistance(a, b)([number,number], [number,number]) => numberGreat-circle distance in metres between two [lon, lat] points
pointInPolygon(point, polygon)([number,number], [number,number][]) => booleanRay-casting check — is point inside polygon?

Barikoi API client methods

All methods are available on the object returned by createBarikoiClient({ apiKey }) from the barikoiapis package.

MethodParametersReturns
autocomplete{ q: string, bangla?: boolean }{ data: { places, status } } — matching places with addresses, coordinates, area, city
reverseGeocode{ longitude, latitude, district?, bangla?, thana?, … }{ data: { place, status } } — address components for a coordinate
nearby{ longitude, latitude, radius?, limit? }{ data: { places, status } } — POIs within radius (km)
geocode{ q: string, district?, bangla?, thana? }{ data: { fixed_address, confidence_score_percentage, geocoded_address, status } }
searchPlace{ q: string }{ data: { places, session_id, status } } — place codes + session ID
placeDetails{ place_code: string, session_id: string }{ data: { place, session_id, status } } — full details for a place code
routeOverview{ coordinates: "lon,lat;lon,lat", geometries?, profile? }{ data: { routes, waypoints, code } } — distance, duration, geometry
calculateRoute{ start: {latitude, longitude}, destination: {latitude, longitude}, type: 'gh', profile? }{ data: { paths, hints, info } } — turn-by-turn instructions, elevation
snapToRoad{ point: "lat,lon" }{ data: { coordinates, distance, type } } — nearest road point
checkNearby{ current_latitude, current_longitude, destination_latitude, destination_longitude, radius }{ data: { message, status } } — geo-fence check
setApiKey(key)stringvoid — update the API key at runtime
getApiKey()(none)string — current API key

See the full barikoiapis README for TypeScript type definitions and detailed parameter docs.


Next steps

  • Markers — Use <Marker id="…" lngLat={[lon, lat]}> to drop custom pins on the map
  • Current location — Request Location.requestForegroundPermissionsAsync() and call Location.getCurrentPositionAsync() to centre the map on the user
  • Shapes — Render <GeoJSONSource> and <Layer> for routes, polygons, or GeoJSON overlays
  • Search & Geocoding — Wire client.autocomplete() to a <TextInput> with debounce for live search suggestions
  • Routing — Call client.routeOverview() or client.calculateRoute() and render the returned GeoJSON geometry as a <Layer>
  • Troubleshooting — Common errors and fixes:
    • Blank map / 401 error — Verify your API key is valid and has not expired.
    • App crashes on launch — Run npx expo prebuild --clean and rebuild. Make sure @maplibre/maplibre-react-native is in plugins.
    • Location not working — Ensure expo-location is installed and you have requested permissions before calling getCurrentPositionAsync.
    • Expo Go shows red screen — Expo Go does not support custom native modules. Use a dev client build instead.