NPM:
npm install @mappedin/react-native-sdk react-native-webview
YARN:
yarn add @mappedin/react-native-sdk react-native-webview
Update cocoapods: cd ios && pod install
We provide both a declarative API using props and an imperative API with methods. For example, to listen to polygon clicks, we provide a prop onPolygonClicked
; in order to focus the map onto this polygon, we provide an imperative API method focusOn
.
Component Signature:
const MiMapView = (props: TMapViewProps) => React.ReactElement;
Prop | Description | Signature | Required |
---|---|---|---|
options | Options to initialize MapView with | TMiMapViewOptions |
Yes |
ref | Exposes Imperative API MapViewStore |
React.MutableRefObject<MapViewStore > |
No |
onPolygonClicked | Gets called when clicking interactive polygon | ({ polygon: MappedinPolygon }) => void |
No |
onDataLoaded | Fired when Mappedin data is loaded |
({ venueData: Mappedin }) => void |
No |
onFirstMapLoaded | Fires when map can first be interacted with | () => void | No |
onBlueDotStateChanged | Fires when BlueDot state changes | ({ stateChange: TBlueDotStateChange }) => void |
No |
onBlueDotPositionUpdated | Fires when BlueDot position changes | ({ update: TBlueDotPositionUpdate }) => void |
No |
onStateChanged | Fires when SDK State changes | ({ state: STATE }) => void |
No |
Documented in Detail here: MapViewStore
Example:
import { Mappedin } from '@mappedin/react-native-sdk';
const options = {
clientId: '****',
clientSecret: '***',
venue: 'venue-slug',
perspective: 'Website',
};
// Render map
const App = () => {
return <MiMapView style={{ flex: 1 }} options={options} />;
};
Example:
import { MiMapView } from '@mappedin/react-native-sdk';
import type { Mappedin } from '@mappedin/react-native-sdk';
const App = () => {
const [venueData, setVenueData] = React.useState<Mappedin>({});
return (
<MiMapView
options={options}
onDataLoaded={({ venueData }) => {
setVenueData(venueData);
}}
/>
);
};
It is possible to fetch venue data (locations, maps, etc), outside of the MiMapView component. This can be helpful for cases where a map isn't always needed or there is a need to fetch the data first, and render the map later.
import { getVenue, MiMapView } from '@mappedin/react-native-sdk';
const options = {
clientId: '****',
clientSecret: '***',
venue: 'venue-slug',
perspective: 'Website',
};
const App = () => {
const [venueData, setVenueData] = React.useState<Mappedin>({});
useEffect(() => {
async function init() {
const venueData = await getVenue(options);
console.log(venueData.locations);
setVenueData(venueData);
}
}, []);
return (
{venueData != null &&
<MiMapView
style={{ flex: 1 }}
options={options}
// pass venueData in
venueData={venueData}
/>
}
);
};
import { MiMapView } from '@mappedin/react-native-sdk';
import type { MappedinLocation, Mappedin } from '@mappedin/react-native-sdk';
const App = () => {
const [venueData, setVenueData] = React.useState<Mappedin>({});
const [
selectedLocation,
setSelectedLocation,
] = React.useState<MappedinLocation>({});
return (
<MiMapView
style={{ flex: 1 }}
options={options}
onDataLoaded={({ venueData: Mappedin }) => {
setVenueData(venueData);
}}
onPolygonClicked={({ polygon }) => {
setSelectedLocation(polygon.locations[0]);
}}
/>
);
};
import { MiMapView } from '@mappedin/react-native-sdk';
import type { MapViewStore } from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const App = () => {
return (
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onFirstMapLoaded={() => {
mapView.current!.Camera.set({ positionOptions: { rotation: 90 });
}}
/>
);
};
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
MappedinPolygon,
MapViewStore,
Mappedin,
} from '@mappedin/react-native-sdk';
const App = () => {
const [venueData, setVenueData] = React.useState<Mappedin>({});
const [
selectedLocation,
setSelectedLocation,
] = React.useState<MappedinLocation>({});
// Imperative API
const mapView = React.useRef<MapViewStore>();
return (
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onDataLoaded={({ venueData: Mappedin }) => {
setVenueData(venueData);
}}
onPolygonClicked={async ({ polygon: MappedinPolygon }) => {
setSelectedLocation(polygon.locations[0]);
mapView.current.clearAllPolygonColors();
mapView.current.setPolygonColor(polygon, 'red');
// lets wait for the focusOn to complete
await mapView.current.Camera.focusOn({ targets: {
polygons: [polygon],
}
});
console.log('success!');
}}
/>
);
};
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
TBlueDotUpdate,
MapViewStore,
MappedinMap,
MappedinNode,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const App = () => {
return (
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onFirstMapLoaded={() => {
mapView.current.BlueDot.enable();
}}
onBlueDotStateChanged={({
stateChange,
}: {
stateChange: TBlueDotStateChange;
}) => {
// This event can be used to get real time updates as to what the nearest node is
const {
map,
nearestNode,
}: {
map: MappedinMap;
nearestNode: MappedinNode;
} = stateChange;
}}
/>
);
};
Setting the SDK into follow mode will lock the camera to follow the BlueDot at the center of the screen. Interacting with the map will put the SDK back into EXPLORE mode and emit onStateChanged event.
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
TBlueDotUpdate,
MapViewStore,
MappedinLocation,
MappedinNode,
} from '@mappedin/react-native-sdk';
import { STATE } from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const nearestNode = React.useRef<MappedinNode>();
const [
selectedLocation,
setSelectedLocation,
] = React.useState<MappedinLocation>({});
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onStateChanged={({ state }) => {
console.log('Current State is', state);
}}
onDataLoaded={() => {
mapView.current.setState(STATE.FOLLOW);
mapView.current.BlueDot.enable();
}}
onBlueStateChanged={({ stateChange }) => {
nearestNode.current = stateChange.nearestNode;
}}
onPolygonClicked={({ polygon: MappedinPolygon }) => {
setSelectedLocation(polygon.locations[0]);
}}
/>
</>
);
};
This API can be used to get a directions object from any MappedinLocation | MappedinNode | MappedinPolygon to any MappedinLocation | MappedinNode | MappedinPolygon. This can be used to show Text directions, as well as draw paths.
Example:
...
<Button
title="Get Directions"
onPress={async () => {
try {
const directions = selectedLocation.directionsTo(destinationLocation);
console.log(directions);
} catch (e) {
console.error(e);
}
}}
></Button>
...
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
MappedinLocation,
MapViewStore,
MappedinPolygon,
} from '@mappedin/react-native-sdk';
const App = () => {
const mapView = React.useRef<MapViewStore>(null);
const [destination, setDestination] = React.useState<MappedinLocation>(null);
const [departure, setDeparture] = React.useState<MappedinLocation>(null);
return (
<>
<MiMapView
style={{flex: 1}}
ref={mapView}
options={options}
onPolygonClicked={({polygon: MappedinPolygon}) => {
// first, let's set departure
if (departure == null) {
setDeparture(polygon.locations[0]);
} else {
// then, destination
setDestination(polygon.locations[0]);
}
}}
/>
<Button
title="Get Directions"
onPress={async () => {
try {
// get Directions
const directions = departure.directionsTo(destination);
// draw path on map
await mapView.current?.Journey.draw(directions);
// focus on the path
mapView.current?.Camera.focusOn({
targets: {
nodes: directions.path,
},
});
} catch (e) {
console.error(e);
}
}}></Button>
<Button
title="Reset"
onPress={() => {
mapView.current?.removeAllPaths();
setDeparture(undefined);
setDestination(undefined);
}}></Button>
</>
);
};
SVG is a popular format for image assets, which means there are a lot of proprietary, broken, or unnecessary tags when getting SVGs online.
Before using SVGs with our SDKs, they need to get “cleaned up” - this also drastically reduces their file size, so win-win.
Once the SVGs are prepared, they need to be wrapped in a div
element which will give them an explicit size. This allows markers to be any desired size and the SVG will grow/shrink appropriately. The element can also add a background, shadows, and any other styles.
`
<div style="width: 32px; height: 32px;">
<svg xmlns="http://www.w3.org/2000/svg">...</svg>
</div>
`
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
MapViewStore,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onFirstMapLoaded={() => {
// create a coordinate
const coordinate = mapView.current.venueData.maps[1].createCoordinate(43.52117572969203, -80.53869317220372);
const coordinate2 = mapView.current.venueData.maps[1].createCoordinate(44.52117572969203, -79.53869317220372);
// find distance between coordinates:
console.log(coordinate.absoluteDistanceTo(coordinate2));
// coordinates can also be used to place markers:
mapView.current.createMarker(coordinate, ...)
}}
/>
)
}
Labels are added on map load by default, but this can be overriden by setting labelAllLocationsOnInit to false and taking control of labeling. We provide 2 themes, and full control over further customization.
import { MiMapView, labelThemes } from '@mappedin/react-native-sdk';
import type {
MapViewStore,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={{ ...options, labelAllLocationsOnInit: false }}
onFirstMapLoaded={() => {
// use one of the themes
mapView.current.labelAllLocations({ appearance: labelThemes.lightOnDark })
// OR, customize a set of properties
mapView.current.labelAllLocations({ appearance: { text: { size: 12 }} })
// OR, do both
mapView.current.labelAllLocations({ appearance:
{
...labelThemes.lightOnDark,
margin: 20,
text: {
...labelThemes.lightOnDark.text,
size: 20
}
}
})
}}
/>
)
}
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
MapViewStore,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const markerId = React.useRef(null);
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onDataLoaded={() => {
mapView.current.BlueDot.enable();
}}
onPolygonClicked={({ polygon }) => {
if (markerId != null) {
mapView.current.removeMarker(markerId);
}
// Let's add a marker anchored to the top of the first entrance to polygon
markerId.current = mapView.current.createMarker(polygon.entrances[0], `
<div style="width: 32px; height: 32px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 293.334 293.334"><g fill="#010002"><path d="M146.667 0C94.903 0 52.946 41.957 52.946 93.721c0 22.322 7.849 42.789 20.891 58.878 4.204 5.178 11.237 13.331 14.903 18.906 21.109 32.069 48.19 78.643 56.082 116.864 1.354 6.527 2.986 6.641 4.743.212 5.629-20.609 20.228-65.639 50.377-112.757 3.595-5.619 10.884-13.483 15.409-18.379a94.561 94.561 0 0016.154-24.084c5.651-12.086 8.882-25.466 8.882-39.629C240.387 41.962 198.43 0 146.667 0zm0 144.358c-28.892 0-52.313-23.421-52.313-52.313 0-28.887 23.421-52.307 52.313-52.307s52.313 23.421 52.313 52.307c0 28.893-23.421 52.313-52.313 52.313z"/><circle cx="146.667" cy="90.196" r="21.756"/></g></svg>
</div>
`,
{
anchor: MARKER_ANCHOR.TOP,
})
}}
/>
)
}
import { MiMapView } from '@mappedin/react-native-sdk';
import type { MapViewStore } from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const App = () => {
return (
<TouchableWithoutFeedback
style={{ flex: 1 }}
onPress={async ({ locationX, locationY }) => {
// get nearest node to X,Y screen coordinate
const node = await mapView.current?.getNearestNodeByScreenCoordinates(
locationX,
locationY,
);
}}
>
<MiMapView style={{ flex: 1 }} ref={mapView} options={options} />
</TouchableWithoutFeedback>
);
};
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
TBlueDotUpdate,
MapViewStore,
MappedinLocation,
MappedinNode,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const nearestNode = React.useRef<MappedinNode>();
const [
selectedLocation,
setSelectedLocation,
] = React.useState<MappedinLocation>({});
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
onDataLoaded={() => {
mapView.current.BlueDot.enable();
}}
onBlueDotStateChanged={({ stateChange }) => {
nearestNode.current = stateChange.nearestNode;
}}
onPolygonClicked={({ polygon: MappedinPolygon }) => {
setSelectedLocation(polygon.locations[0]);
}}
/>
<Button
title="Get Directions"
onPress={async () => {
try {
// get Directions
const directions = nearestNode.current.directionsTo(selectedLocation);
// draw path and connections on map
await controller.current?.Journey.draw(directions);
// focus on the path
controller.current?.Camera.focusOn({ targets: {
nodes: directions.path,
}
});
} catch (e) {
console.error(e);
}
}}
></Button>
</>
);
};
import { TextInput } from 'react-native';
import { MiMapView } from '@mappedin/react-native-sdk';
import type {
TBlueDotUpdate,
MapViewStore,
MappedinLocation,
MappedinNode,
} from '@mappedin/react-native-sdk';
// Imperative API
const mapView = React.useRef<MapViewStore>();
const [text, setText] = React.useState('');
const search = async (text) => {
const searchResults = await mapView.current.OfflineSearch.search(text);
// do something with search results
console.log(searchResults);
};
useEffect(() => {
const suggest = async (text) => {
const searchSuggestions = await mapView.current.OfflineSearch.suggest(text);
// do something with search suggestions
console.log(searchSuggestions);
};
if (text !== '') {
suggest(text);
}
}, [text]
const App = () => {
return (
<>
<MiMapView
style={{ flex: 1 }}
ref={mapView}
options={options}
/>
<View>
<TextInput
onChangeText={(text) => setText(text)}
value={text}
/>
<Button title="Search" onClick={() => search(setText)} />
</View>
</>
);
};
react-native-location
to power BlueDotimport RNLocation from 'react-native-location';
const App = () => {
...
RNLocation.requestPermission({
ios: 'whenInUse',
android: {
detail: 'coarse',
},
}).then((granted) => {
if (granted) {
// Enable blue dot
mapView.current.BlueDot.enable();
RNLocation.subscribeToLocationUpdates((locations) => {
const {
accuracy,
floor,
latitude,
longitude,
timestamp,
} = locations[0];
const location = {
timestamp,
coords: {
latitude,
longitude,
accuracy,
floorLevel: floor,
},
};
// pass locations in
mapView.current.overrideLocation(location);
});
}
});
...
};
Component Signature:
const MiMiniMap = (props: TMiniMapProps) => React.ReactElement;
Prop | Description | Signature | Required |
---|---|---|---|
options | Options to initialize MapView with | TMiMapViewOptions |
Yes |
location | Options to initialize MapView with | MappedinLocation | string |
Yes |
onLoad | Gets called when minimap is rendered | () => void | No |
polygonHighlightColor | Color to use when highlighting polygon | string |
No |
focusOptions | Override focus Options when generating minimap | TFocusOptions |
No |
Example:
const App = () => {
return (
<MiMiniMap
style={{ width: 500, height: 200 }}
options={options: TGetVenueOptions}
/**
* Called when miniMap is rendered
*/
onLoad={() => {}}
/**
* What color to use when highlighting polygon
*/
polygonHighlightColor={string}
/**
* Finer control over focus
*/
focusOptions={options: TFocusOptions}
location={selectedLocation: MappedinLocation | MappedinLocation["id"]}
/>
);
};
It is possible to download the venue bundle with all assets built in, which allows for caching/offline solutions.
Note 1: This must be enabled by Mappedin's Customer Solutions team.
Note 2: This may slow down map rendering for large venues, especially those with many images. We have improvements to this on our roadmap.
import {
getVenueBundle,
MiMapView,
Mappedin,
} from '@mappedin/react-native-sdk';
import { View } from 'react-native';
import React, { useEffect } from 'react';
import fs from 'react-native-fs';
const options = {
clientId: 'clientId',
clientSecret: 'clientSecret',
venue: 'venue-slug',
perspective: 'Website',
};
const App = () => {
const [venueData, setVenueData] = React.useState<Mappedin>();
useEffect(() => {
async function init() {
const path = fs.DocumentDirectoryPath + '/bundle.json';
try {
// let's check if cache exists
if (!(await fs.exists(path))) {
console.log('cache doesnt exist');
const venue = await getVenueBundle(options);
setVenueData(venue);
fs.writeFile(path, venue.toString());
} else {
console.log('cache exists, using');
const venueString = await fs.readFile(path);
const venue = new Mappedin(options);
// hydrate the instance with cached data
venue.hydrate(venueString);
setVenueData(venue);
}
} catch (e) {
console.error(e);
}
}
init();
}, []);
return (
<View style={{ flex: 1 }}>
{venueData != null && (
<MiMapView
style={{ flex: 1 }}
options={options}
// pass venueData in
venueData={venueData}
/>
)}
</View>
);
};