React Native Tricks #2 — Map Performance

Furkan ŞAHİN
4 min readFeb 10, 2023

--

For better performance on Map, Backend must return markers for focused area.

Returning all markers from Backend and rendering all of those markers is a huge problem for performance. So Backend must return markers for focused area and you just have to deal with the markers in the focused area.

What is focused area?

After user zooms in or focuses on a location in map, it is the rectangle area in the screen.

https://medium.com/@abegehr/cluster-react-native-maps-markers-with-react-native-map-clustering-72f50db26891

Solution :

1- Have a look at the onRegionChangeComplete prop of MapView in the code below. Purpose of onRegionChangeComplete is “Callback that is called once when the region changes, such as when the user is done moving the map.”

2- onRegionChangeComplete invokes handlePositionChange function. In the handlePositionChange function, I calculate the coordination of this rectangle area by invoking calculateScreenPolygon. The calculation is basically that :

{
tlat: reg.latitude + reg.latitudeDelta / 2,
blat: reg.latitude - reg.latitudeDelta / 2,
llng: reg.longitude - reg.longitudeDelta / 2,
rlng: reg.longitude + reg.longitudeDelta / 2,
}

3- After that send those 4 params ( top latitude, bottom latitude, left longitude, right longitude ) to the backend by invoking getMarkers function.

4- According to the top latitude & bottom latitude & left longitude & right longitude, a Polygon must be drawn in the backend and backend returns the markers those are placed in this Polygon (Basically the focused area is the Polygon). Actually the most of the work is in the Backend.

async function handlePositionChange(reg: RegionProps) {
const localPosition: PositionProps = calculateScreenPolygon(reg);
setTimeout(async () => {
await getMarkers(localPosition);
}, 500);
}

function calculateScreenPolygon(reg: RegionProps): PositionProps {
const localPosition: PositionProps = {
tlat: reg.latitude + reg.latitudeDelta / 2,
blat: reg.latitude - reg.latitudeDelta / 2,
llng: reg.longitude - reg.longitudeDelta / 2,
rlng: reg.longitude + reg.longitudeDelta / 2,
};
return localPosition;
}


async function getMarkers(coordinates: PositionProps) {
const result = await GetMarkersRequest(coordinates, 'LESS');
return result instanceof Array ? result : [];
}

<MapView
ref={mapRef}
initialRegion={region}
onMapReady={async () => await findCoordinates(setRegion, mapRef)}
onRegionChangeComplete={handlePositionChange}
showsUserLocation={true}
showsMyLocationButton={false}
zoomEnabled={true}
style={styles.map}
>

— — — —

GetMarkersRequest code with search params :

import api from '../../index';
import { IResponse, PositionProps } from '../../../interface';

function setSearchParams(coordinates: PositionProps, detail: string) {
const searchParams = new URLSearchParams();

if (coordinates.llng !== 0) {
searchParams.append('blat', coordinates.blat.toString());
searchParams.append('tlat', coordinates.tlat.toString());
searchParams.append('llng', coordinates.llng.toString());
searchParams.append('rlng', coordinates.rlng.toString());
}

if (detail) {
searchParams.append('detail', detail.toUpperCase());
}

return searchParams;
}

const getMarkers = async (coordinates: PositionProps, detail: string) => {
const path = '/markers';
const searchParams = setSearchParams(coordinates, detail);

return await api
.GET(path, {
params: searchParams,
})
.then((result: IResponse) => {
if (result.success) {
return result.data;
} else {
return result.error;
}
});
};

export default getMarkers;

Code for extended Axios — import api from ‘../../index’ :

import { ApiHelper } from './serverConnections/';
import { API_URL } from '@env';

const api = new ApiHelper({
baseURL: API_URL,
timeout: 3000,
});

export default api;
import axios, { AxiosInstance } from 'axios';
import { ReRequest } from '../requests';
import { IResponse } from '../../../interface';

export interface ApiHelperOptions {
baseURL: string;
timeout?: number;
}

export default class ApiHelper {
api: AxiosInstance;
token: string | null | undefined;
lang: string | null;

constructor(options: ApiHelperOptions) {
const { baseURL, timeout = 10000 } = options;
this.api = axios.create({
baseURL,
timeout,
headers: { 'Content-Type': 'application/json' },
});

this.api.interceptors.request.use((prevConfig: any) => {
const { ...config } = prevConfig;

if (this.lang) {
config.headers['Accept-Language'] = this.lang;
}

if (this.token) {
config.headers.Authorization = this.token;
}

return config;
});
}

// call this after login/refresh & application start
setToken = (token: string, type = 'Bearer'): void => {
this.token = `${type} ${token}`;
};

// call this after the language changed & application start
setLanguage = (lang: string): void => {
this.lang = lang;
};

POST = (path: string, data: any, config?: {}) => {
return this.api
.post(path, data, config)
.then((response: any) => {
return this.controlResponse(response);
})
.catch((error: any) => {
return this.catchHandler(error);
});
};

DELETE = (path: string, data: any) => {
return this.api
.delete(path, data)
.then((response: any) => {
return this.controlResponse(response);
})
.catch((error: any) => {
return this.catchHandler(error);
});
};

GET = (path: string, config?: {}) => {
return this.api
.get(path, config)
.then((response: any) => {
return this.controlResponse(response);
})
.catch((error: any) => {
return this.catchHandler(error);
});
};

PUT = (path: string, data: any, config?: {}) => {
return this.api
.put(path, data, config)
.then((response: any) => {
return this.controlResponse(response);
})
.catch((error: any) => {
return this.catchHandler(error);
});
};

PATCH = (path: string, data: any, config?: {}) => {
return this.api
.patch(path, data, config)
.then((response: any) => {
return this.controlResponse(response);
})
.catch((error: any) => {
return this.catchHandler(error);
});
};

catchHandler = (error: any): IResponse => {
if (error?.response) {
return this.controlResponse(error.response);
} else {
return {
data: null,
error: error.message,
success: false,
status: 500,
};
}
};

controlResponse = async (response: any): IResponse => {
if (response.status >= 200 && response.status <= 208) {
return {
data: response.data,
success: true,
status: response.status,
};
} else if (response.status === 401 && response.config.url !== '/auth/login') {
return await ReRequest(response.config); // refreshing token and doing request again
} else {
return {
data: response.data,
error:
response?.data?.error?.message !== undefined
? response.data.error.message
: response?.data?.message !== undefined
? response.data.message
: 'Error',
success: false,
status: response.status,
};
}
};
}

If you think there is something wrong, do not hesitate to contact me!

Discover my React Native Template

--

--

No responses yet