Customizable map styles in React Native and Mapbox
Akadenia recently implemented a feature that allows users add custom styles to an app built on top of Mapbox services. There are many valid reasons to choose Mapbox when it comes to adding map services to your React Native app. The Mapbox ecosystem which is made up of Web, iOS and Android SDKs along with their incredible documentation which makes it quite straight-forward to implement various map and geo-location services in your app. If you're building an app using React Native, while there is no official React Native library from Mapbox team, we use a community package called @rnmapbox/maps that's well-maintained and endorsed on the official Mapbox website.
Let's dive into how to add custom map styles to a React Native app using @rnmapbox/maps.
Obtaining an access token
Before using any of the Mapbox SDKs, we first need an access token that will allow us to access Mapbox services. We can obtain the access token by creating an account on https://mapbox.com . Once you login to your Mapbox account, browse to the Tokens page and you can start with the default token or create a specific token for an app or service.
The default token will allow us to load a map in our app but it does not allow us to add new map styles. So we're going to need to create a new access token with the styles:write permission. It is also recommended to add the styles:read permissions too since we'll eventually need to load that style. Access token permissions can be updated after the fact so it's good practice to only add the permissions you need when you need them.
Setting up a Map View in our application
Now that we've obtained our access token, Let's use @rnmapbox/maps to load a map in our ReactNative app. The library installation process is straight forward by following the installation instructions in the documentation and using the map view component.
import MapboxGL from "@rnmapbox/maps"
const MAPBOX_API_KEY = "your-api-key"
MapboxGL.setAccessToken(MAPBOX_API_KEY)
In our code, our component will look like this:
<MapboxGL.MapView style={{width: “100%”, height: “100%” }} styleURL=”mapbox://styles/mapbox/streets-v12”/>
Our app will look like this:
Mapbox mobile app view
It's worth noting that styles passed to the map view should be based on the context of how the elements are arranged in our screen component. In addition, the styleURL prop is optional and if we don't pass it then the default Mapbox street style will be used. That said, this is all we need to load a map style.
Uploading a new Map Style - Validation
There are two ways we can allow users to add a MapStyle in our app:
- Allow users to submit style URLs and save them in our database. One constraint of this approach is that we cannot load private map styles in our app unless they were created with our access token (which proves ownership)
- Allow users to upload a JSON file that fits the Mapbox style specification, send that JSON via the Styles API to Mapbox, receive a new style URL in the response and save the URL to our database
Regardless which approach you pick, we'll need some validation. Here's how we would validate a style URL using some regex.
export const validateMapboxStyleUrl = ({ url }) => {
const mapboxStyleUrlValidator = /^mapbox:\/\/styles\/+[A-Za-z_-]+\//g;
return mapboxStyleUrlValidator.test(url);
};
For complex object schema validation we're going to rely on yup, a popular validation library for node.
import * as yup from "yup";
export const mapboxJsonValidator = yup.object().shape({
version: yup.number().required(),
name: yup.string().required(),
metadata: yup
.object()
.shape({
"mapbox:origin": yup.string().optional(),
"mapbox:autocomposite": yup.boolean().optional(),
"mapbox:type": yup.string().optional(),
"mapbox:sdk-support": yup
.object()
.shape({
js: yup.string().optional(),
android: yup.string().optional(),
ios: yup.string().optional(),
}),
})
.optional(),
center: yup.array().of(yup.number()).required(),
zoom: yup.number().required(),
bearing: yup.number().required(),
pitch: yup.number().required(),
sources: yup
.object()
.shape({
composite: yup
.object()
.shape({ url: yup.string().required(), type: yup.string().required() }),
}),
sprite: yup.string().optional(),
glyphs: yup.string().optional(),
layers: yup.array().of(yup.object()).required(),
created: yup.string().optional(),
id: yup.string().required(),
modified: yup.string().optional(),
owner: yup.string().required(),
visibility: yup.string().required(),
draft: yup.boolean().required(),
});
Then use our validator this way
const validatorSuccess = mapboxJsonValidator
.validate(mapboxJsonMapStyle)
.then(() => true)
.catch(() => false);
Uploading a new Map Style - Using the Styles API
If we're taking a URL after validating it, we can immediately send it to our database. However, if we're taking a JSON file, after validating on the client, we still need to send the style data to Mapbox to get a style URL that we can actually load.
We'll upload our style to the URL below via a REST POST request with our JSON object passed as data in our request. We also need to specify the content type header as content-type: application/json
URL: https://api.mapbox.com/styles/v1/your-mapbox-user-name?access\_token=<YOUR\_MAPBOX\_ACCESS\_TOKEN?
Once the request is successful we'll get a style URL in the response that we can save in our database.
Loading styles
Now all that's needed to load styles in our app is to retrieve them from the database via any HTTP client like Node's fetch API, Axios or another library that allows us to pass them to our map view via any kind of state mechanism.
Demo
Here's an example of how the map looks like after we load a custom style