If your app pays drivers per trip, checks employees in by location, or rewards players for visiting places, someone will eventually try a fake GPS app. On Android, mock-location apps are a few taps away in Developer options. The good news is that Android marks fixes coming from a mock provider, and a small React Native package exposes that flag to JavaScript.
How the detector works
The package react-native-mock-location-detector (latest published version 2.0.0) is a thin native module. Its default export, RNMockLocationDetector, has exactly one method: checkMockLocationProvider(), which returns a Promise<boolean>. The README mentions passing a dialog title, text and button label, but the actual code takes no arguments and shows no dialog. Any UI is yours to build.
On the native side it asks Google Play services' fused location provider for the last known location via FusedLocationProviderClient.getLastLocation(), then calls Location.isFromMockProvider() on it (available since API 18). On very old devices it falls back to the Settings.Secure.ALLOW_MOCK_LOCATION setting.
Install and link
Modern React Native autolinks native modules, so there is no react-native link step. You do need a native rebuild, and the device needs Google Play services because the check uses the fused provider. The package's own manifest already declares ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION.
npm install react-native-mock-location-detector
# rebuild the Android app so the native module is compiled in
npx react-native run-androidA useMockLocationGuard hook
Wrap the permission request and the check in a hook that exposes a clear state. Because the package is Android only, guard with Platform.OS so iOS builds never call a module that does not exist. Re-check whenever the app returns to the foreground with AppState: users often switch to their spoofing app and back.
import { useCallback, useEffect, useState } from 'react';
import { AppState, PermissionsAndroid, Platform } from 'react-native';
import RNMockLocationDetector from 'react-native-mock-location-detector';
export type GuardState =
| 'checking'
| 'ok'
| 'mocked'
| 'no-permission'
| 'unsupported';
export function useMockLocationGuard() {
const [state, setState] = useState<GuardState>('checking');
const check = useCallback(async () => {
if (Platform.OS !== 'android') {
setState('unsupported');
return;
}
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
);
if (result !== PermissionsAndroid.RESULTS.GRANTED) {
setState('no-permission');
return;
}
const mocked = await RNMockLocationDetector.checkMockLocationProvider();
setState(mocked ? 'mocked' : 'ok');
}, []);
useEffect(() => {
check();
const sub = AppState.addEventListener('change', (next) => {
if (next === 'active') check();
});
return () => sub.remove();
}, [check]);
return { state, recheck: check };
}PermissionsAndroid.request returns immediately with GRANTED if the user already allowed location, so calling it on every check is cheap. If the user picked "Don't allow" permanently, it returns NEVER_ASK_AGAIN, and your no-permission screen should explain how to enable it in system settings.
Gate the experience, kindly
When a mock provider is detected, block only the location-sensitive flows and explain what to do. Most users who trip the check are developers or testers who forgot a mock app was enabled, not fraudsters. The exact path to Developer options differs between manufacturers, so describe it in general terms rather than promising a specific screen.
import type { ReactNode } from 'react';
import { ActivityIndicator, Button, Text, View } from 'react-native';
import { useMockLocationGuard } from './useMockLocationGuard';
export function MockLocationGate({ children }: { children: ReactNode }) {
const { state, recheck } = useMockLocationGuard();
if (state === 'checking') {
return <ActivityIndicator style={{ flex: 1 }} />;
}
if (state === 'mocked') {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 24, gap: 12 }}>
<Text style={{ fontSize: 22, fontWeight: '600' }}>Turn off mock location</Text>
<Text>
A mock location app is active on this device. Disable it in
Developer options (or uninstall it), then come back.
</Text>
<Button title="I've turned it off" onPress={recheck} />
</View>
);
}
if (state === 'no-permission') {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 24, gap: 12 }}>
<Text>Location access is needed to continue.</Text>
<Button title="Try again" onPress={recheck} />
</View>
);
}
return <>{children}</>;
}Moving to the maintained fork for iOS
The original package's README states that the author no longer maintains it, and recommends react-native-turbo-mock-location-detector by jpudysz. The fork supports both the old and new React Native architectures and adds iOS. On Android it uses location.isMock on API 31 and above, and isFromMockProvider below that. On iOS 15 and above it reads CLLocationSourceInformation.isSimulatedBySoftware.
import {
isMockingLocation,
MockLocationDetectorErrorCode,
type MockLocationDetectorError,
} from 'react-native-turbo-mock-location-detector';
export type MockCheck = 'ok' | 'mocked' | 'gps-off' | 'no-permission' | 'unknown';
export async function checkMockLocation(): Promise<MockCheck> {
try {
const { isLocationMocked } = await isMockingLocation();
return isLocationMocked ? 'mocked' : 'ok';
} catch (e) {
const error = e as MockLocationDetectorError;
switch (error.code) {
case MockLocationDetectorErrorCode.GPSNotEnabled:
return 'gps-off';
case MockLocationDetectorErrorCode.NoLocationPermissionEnabled:
return 'no-permission';
case MockLocationDetectorErrorCode.CantDetermine:
default:
return 'unknown'; // always the case on iOS below 15
}
}
}Unlike the original, the fork rejects with a { code, message } error instead of quietly returning false, which makes the "could not check" case explicit. Run pod install in the ios folder after installing, and note that it will not work in Expo Go: Expo projects need a development build that includes the native module.
Key takeaways
- —
react-native-mock-location-detectorexposes one Android-only method,checkMockLocationProvider(), which takes no arguments. - —It inspects the last known fused location, so request permission and get a fix first;
falseonly means nothing was detected. - —Wrap it in a hook, guard with
Platform.OS, and re-check when the app returns to the foreground. - —Block only location-sensitive flows and explain how to turn the mock app off.
- —For iOS 15+, the new architecture and explicit error codes, use
react-native-turbo-mock-location-detector.
Detection on the device is only half the story. Next, read Why Client-Side Fake GPS Checks Aren't Enough to see how to validate location on the server with speed, time and integrity signals.
