Skip to main content

Geofencing

Geofencing allows your application to define virtual geographic boundaries (circular regions) and receive notifications when the device enters, exits, or dwells within those regions.

What Is Geofencing?

A geofence is a virtual circular boundary defined by a center coordinate (latitude/longitude) and a radius. When the device crosses the boundary, the system fires a transition event. The library supports three transition types:

  • ENTER -- The device entered the geofence region.
  • EXIT -- The device exited the geofence region.
  • DWELL -- The device remained inside the region for a configured duration.

Common use cases include store proximity alerts, fleet management zones, attendance tracking, delivery area monitoring, and safety perimeters.

Geofencing operates entirely in the background. Once registered, geofences are monitored by the operating system even when the app is not in the foreground.

Platform Implementations

PlatformTechnologyMax Geofences
AndroidGoogle Play Services GeofencingClient100
iOSCLLocationManager region monitoring20

Prerequisites

Before using geofencing:

  1. Location permissions: Background location access is required. Use useLocationPermissions and call requestPermissions() before registering geofences. See the Permission Handling guide.
  2. Android: Google Play Services must be installed. Without it, a GeofenceError with code PLAY_SERVICES_UNAVAILABLE is thrown.
  3. iOS: "Always" authorization is required for reliable background monitoring. "When In Use" works only in the foreground.
  4. Minimum versions: iOS 13+, Android SDK 24+.

Quick Start

A minimal example that registers a geofence and listens for transitions:

import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import {
useGeofencing,
useGeofenceEvents,
GeofenceTransitionType,
} from '@gabriel-sisjr/react-native-background-location';

function GeofenceDemo() {
const [enterCount, setEnterCount] = useState(0);
const { geofences, addGeofence, maxGeofences } = useGeofencing();

useGeofenceEvents({
onTransition: (event) => {
if (event.transitionType === GeofenceTransitionType.ENTER) {
setEnterCount((prev) => prev + 1);
}
},
});

const handleAddGeofence = async () => {
await addGeofence({
identifier: 'office',
latitude: -23.5505,
longitude: -46.6333,
radius: 200,
});
};

return (
<View>
<Text>Active: {geofences.length} / {maxGeofences}</Text>
<Text>Enter events: {enterCount}</Text>
<Button title="Add Office Geofence" onPress={handleAddGeofence} />
</View>
);
}

GeofenceRegion Definition

The GeofenceRegion interface defines a circular geofence.

PropertyTypeRequiredDefaultDescription
identifierstringYes--Unique identifier (consumer-provided)
latitudenumberYes--Center latitude (-90 to 90)
longitudenumberYes--Center longitude (-180 to 180)
radiusnumberYes--Radius in meters (minimum 100)
transitionTypesGeofenceTransitionType[]No[ENTER, EXIT]Which transitions to monitor
loiteringDelaynumberNo30000Milliseconds before DWELL fires
expirationDurationnumberNoIndefiniteAuto-expire after this many ms
metadataRecord<string, unknown>NoundefinedArbitrary JSON-serializable data
notificationOptionsNotificationOptions | falseNoundefinedPer-geofence notification override
import {
addGeofence,
GeofenceTransitionType,
} from '@gabriel-sisjr/react-native-background-location';

await addGeofence({
identifier: 'warehouse-north',
latitude: 40.7128,
longitude: -74.006,
radius: 300,
transitionTypes: [
GeofenceTransitionType.ENTER,
GeofenceTransitionType.EXIT,
GeofenceTransitionType.DWELL,
],
loiteringDelay: 60000,
metadata: { zone: 'loading-dock', priority: 'high' },
});

Adding Geofences

Single Geofence

import { addGeofence } from '@gabriel-sisjr/react-native-background-location';

await addGeofence({
identifier: 'office',
latitude: -23.5505,
longitude: -46.6333,
radius: 200,
});

Batch Registration

Register multiple geofences atomically. All succeed or all fail -- no partial registration occurs.

import { addGeofences } from '@gabriel-sisjr/react-native-background-location';

await addGeofences([
{
identifier: 'zone-a',
latitude: -23.5505,
longitude: -46.6333,
radius: 200,
},
{
identifier: 'zone-b',
latitude: -23.5612,
longitude: -46.6558,
radius: 150,
},
]);

Removing Geofences

import {
removeGeofence,
removeGeofences,
removeAllGeofences,
} from '@gabriel-sisjr/react-native-background-location';

// Remove a single geofence
await removeGeofence('office');

// Remove multiple geofences
await removeGeofences(['zone-a', 'zone-b']);

// Remove all geofences
await removeAllGeofences();

Querying Active Geofences

import {
getActiveGeofences,
getMaxGeofences,
} from '@gabriel-sisjr/react-native-background-location';

const active = await getActiveGeofences();
const limit = await getMaxGeofences();
console.log(`Monitoring ${active.length} / ${limit} geofences`);

The useGeofencing Hook

The useGeofencing hook provides a complete CRUD interface with state management, loading indicators, and automatic refresh after every mutation.

Options

OptionTypeDefaultDescription
autoLoadbooleantrueFetch active geofences on mount
notificationOptionsNotificationOptionsundefinedGlobal notification config for transitions

Return Values

ValueTypeDescription
geofencesGeofenceRegion[]Currently active geofence regions
isLoadingbooleanWhether an async operation is in progress
errorError | nullLast error, or null
addGeofence(region) => Promise<void>Register a single geofence
addGeofences(regions) => Promise<void>Register multiple geofences atomically
removeGeofence(id) => Promise<void>Remove a geofence by identifier
removeGeofences(ids) => Promise<void>Remove multiple geofences
removeAllGeofences() => Promise<void>Remove all geofences
maxGeofencesnumber | nullPlatform limit, or null if not yet loaded
refresh() => Promise<void>Manually reload from native
clearError() => voidClear error state

Full CRUD Example

import React from 'react';
import { View, Text, Button, FlatList, Alert } from 'react-native';
import { useGeofencing } from '@gabriel-sisjr/react-native-background-location';

function GeofenceManager() {
const {
geofences,
isLoading,
error,
addGeofence,
removeGeofence,
removeAllGeofences,
maxGeofences,
refresh,
clearError,
} = useGeofencing();

const handleAdd = async () => {
try {
await addGeofence({
identifier: `zone-${Date.now()}`,
latitude: -23.5505,
longitude: -46.6333,
radius: 200,
});
} catch (err) {
Alert.alert('Failed', (err as Error).message);
}
};

return (
<View>
<Text>
Active: {geofences.length} / {maxGeofences ?? '...'}
</Text>

{error && (
<View>
<Text>Error: {error.message}</Text>
<Button title="Dismiss" onPress={clearError} />
</View>
)}

<Button title="Add" onPress={handleAdd} disabled={isLoading} />
<Button title="Remove All" onPress={removeAllGeofences} disabled={isLoading} />
<Button title="Refresh" onPress={refresh} disabled={isLoading} />

<FlatList
data={geofences}
keyExtractor={(item) => item.identifier}
renderItem={({ item }) => (
<View>
<Text>
{item.identifier}: ({item.latitude}, {item.longitude}) r={item.radius}m
</Text>
<Button
title="Remove"
onPress={() => removeGeofence(item.identifier)}
/>
</View>
)}
/>
</View>
);
}

Listening for Transitions with useGeofenceEvents

The useGeofenceEvents hook subscribes to real-time geofence transition events. It is a side-effect-only hook that returns void. Use it alongside useGeofencing for a complete solution.

Options

OptionTypeDefaultDescription
onTransition(event) => voidundefinedCallback on each matching transition
filterGeofenceTransitionType[]undefinedOnly emit these transition types
geofenceIdstringundefinedOnly emit events for this geofence

Listen to All Transitions

import { useGeofenceEvents } from '@gabriel-sisjr/react-native-background-location';

useGeofenceEvents({
onTransition: (event) => {
console.log(
`${event.transitionType} at ${event.geofenceId} ` +
`(${event.distanceFromCenter}m from center)`
);
},
});

Filter by Transition Type

import {
useGeofenceEvents,
GeofenceTransitionType,
} from '@gabriel-sisjr/react-native-background-location';

useGeofenceEvents({
onTransition: (event) => {
showNotification(`Entered ${event.geofenceId}`);
},
filter: [GeofenceTransitionType.ENTER],
});

Filter by Geofence ID

useGeofenceEvents({
onTransition: (event) => {
markAttendance(event.timestamp);
},
geofenceId: 'office-hq',
});

GeofenceTransitionEvent

The event object received in onTransition:

PropertyTypeDescription
geofenceIdstringIdentifier of the triggered geofence
transitionTypeGeofenceTransitionTypeENTER, EXIT, or DWELL
latitudenumberDevice latitude at transition
longitudenumberDevice longitude at transition
timestampstringISO 8601 timestamp
distanceFromCenternumberDistance from geofence center in meters
metadataRecord<string, unknown>Metadata from the geofence (if set)

Error Handling

All geofencing functions throw GeofenceError on failure. The error includes a code property.

import {
addGeofence,
GeofenceError,
GeofenceErrorCode,
} from '@gabriel-sisjr/react-native-background-location';

try {
await addGeofence({
identifier: 'office',
latitude: -23.5505,
longitude: -46.6333,
radius: 200,
});
} catch (error) {
if (error instanceof GeofenceError) {
switch (error.code) {
case GeofenceErrorCode.DUPLICATE_IDENTIFIER:
console.warn('Geofence already exists');
break;
case GeofenceErrorCode.LIMIT_EXCEEDED:
Alert.alert('Limit reached', 'Remove some geofences first');
break;
case GeofenceErrorCode.PERMISSION_DENIED:
console.error('Location permission required');
break;
}
}
}

Error Codes

CodeDescription
INVALID_REGIONCoordinates out of range, radius below 100m, or negative loitering delay
DUPLICATE_IDENTIFIERIdentifier already in use by an active geofence
LIMIT_EXCEEDEDPlatform geofence limit reached (Android: 100, iOS: 20)
MONITORING_FAILEDNative monitoring could not start
NOT_AVAILABLENative module not linked or not available
PERMISSION_DENIEDBackground location permission not granted
PLAY_SERVICES_UNAVAILABLEGoogle Play Services not available (Android only)

Platform Limitations

FeatureAndroidiOS
Maximum geofences10020
Minimum radius100m100m
DWELL detectionNative (GeofencingClient)Software timer
PersistenceRoom DB + BootCompletedReceiverCLLocationManager (system-managed)
Background deliveryBroadcastReceiver (even when killed)Delegate (relaunches app)
RequirementsGoogle Play Services"Always" authorization
Event storageRoom DatabaseCore Data

iOS note: The 20-region limit is shared across all region monitoring, including iBeacon regions. The effective limit may be lower if other monitoring is active.

Android note: Geofences are cleared when Google Play Services data is cleared. The library re-registers on boot via BootCompletedReceiver, but this does not work if the app has been force-stopped.

Transition Events Storage

Transition events are persisted on-device and survive app restarts.

import {
getGeofenceTransitions,
clearGeofenceTransitions,
} from '@gabriel-sisjr/react-native-background-location';

// Get all stored transitions
const allTransitions = await getGeofenceTransitions();

// Get transitions for a specific geofence
const officeTransitions = await getGeofenceTransitions('office');

// Clear transitions for a specific geofence
await clearGeofenceTransitions('office');

// Clear all transitions
await clearGeofenceTransitions();

Combining Geofencing with Tracking

Geofencing and location tracking are independent features that can run simultaneously.

import React, { useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import {
useGeofencing,
useGeofenceEvents,
useBackgroundLocation,
GeofenceTransitionType,
} from '@gabriel-sisjr/react-native-background-location';
import type { GeofenceTransitionEvent } from '@gabriel-sisjr/react-native-background-location';

function GeofenceMonitor() {
const [events, setEvents] = useState<GeofenceTransitionEvent[]>([]);
const { geofences, maxGeofences } = useGeofencing();
const { isTracking, startTracking, stopTracking } = useBackgroundLocation();

useGeofenceEvents({
onTransition: (event) => {
setEvents((prev) => [event, ...prev].slice(0, 50));
},
});

return (
<View>
<Text>Geofences: {geofences.length} / {maxGeofences}</Text>
<Text>Tracking: {isTracking ? 'Active' : 'Stopped'}</Text>
<Text>Events: {events.length}</Text>
<FlatList
data={events}
keyExtractor={(_, i) => i.toString()}
renderItem={({ item }) => (
<Text>
{item.transitionType} {item.geofenceId} at {item.timestamp}
</Text>
)}
/>
</View>
);
}

Next Steps