Skip to content
 
 

Latest commit

 

History

103 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polyfence React Native

The Polyfence geofence layer for React Native. Define zones once; same zones run on your mobile app, your IoT device, and your server (Polyfence platform). This package is the React Native bridge over polyfence-core (engine source: github.com/polyfence/polyfence-core). Privacy-first by default — positions never leave the device; only zone events.

npm version CI License: MIT Platform: Android & iOS

Map view Zones with live state

The screenshots above are from the example app in this repo — a working iOS + Android React Native app that fetches zones from the Polyfence SaaS, tracks location, and renders enter / exit / dwell events. Sign up at polyfence.io for a free API key, then follow example/README.md to run it locally.

Who this is for

You're building a React Native app that needs geofencing — delivery, logistics, fitness, healthcare, asset tracking, agritech, fleet, or consumer. You want the math on-device, the zones defined once, and the same definitions reusable on your IoT firmware or server when you grow into those surfaces.

This package is the React Native bridge. The same engine runs on iOS/Android via polyfence-core, on embedded MCUs via polyfence-embedded, and server-side via the Polyfence API.

Why Polyfence?

  • Polygon geofencing — Not just circles. Define zones with arbitrary polygon boundaries (complex city zones, campus outlines, delivery areas).
  • Unlimited zones — No artificial limits. Monitor hundreds of zones simultaneously with zone clustering for performance.
  • Privacy-first — All geofencing runs on-device. Zero location data ever leaves the device by default. No cloud dependency.
  • SmartGPS — Intelligent GPS scheduling based on proximity, movement, activity, and battery state. 40-50% less battery drain than naive polling.
  • Activity recognition — Automatically detect user activity (walking, driving, stationary) and optimize GPS intervals.
  • Background tracking — True background operation with foreground service (Android) and location background mode (iOS).
  • TypeScript-first — Full type definitions. Built for type safety.
  • Dwell detection — Detect when users stay in zones for a configured duration.

Where your zones come from

Three storage choices — same plugin API in all cases:

Approach Backend API Key Best For
Hardcode zones in your app None Not needed Static zones, full control, privacy-first apps
Fetch from your own API Your backend Not needed Existing infrastructure, custom zone logic
Use the Polyfence dashboard polyfence.io Required Visual zone editor, analytics dashboard

Same plugin API in all cases. The Polyfence platform layer (SDK + dashboard + API) is the geofence layer underneath your product, whether you store zones in code or in the dashboard.


Requirements

Requirement Version
React Native 0.73+
Node.js 18.0+
Android API 24+ (Android 7.0), tested up to API 35 (Android 15)
iOS 14.0+

Platform Support

Feature Android iOS
Circle geofences Yes Yes
Polygon geofences Yes Yes
Dwell detection Yes Yes
Zone clustering Yes Yes
Scheduled tracking Yes Yes
Activity recognition Yes Yes
Background tracking Yes (foreground service) Yes ("Always" permission)
Battery optimization bypass Yes N/A
GPS accuracy profiles Yes Partial (iOS manages GPS)

Installation

npm install polyfence-react-native
# or
yarn add polyfence-react-native

Current version: 2.0.3

Native dependency: Polyfence uses polyfence-core for native geofencing engines. It's included automatically — Maven for Android, CocoaPods for iOS. On iOS, run cd ios && pod install after adding the dependency.

cd ios && pod install

Platform Setup

Android — android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

Ensure your android/app/build.gradle has the correct minimum SDK version:

android {
    defaultConfig {
        minSdkVersion 24 // Required for Polyfence
    }
}

Foreground Service Notification: Polyfence requires a foreground service notification on Android. The plugin automatically creates the notification channel — no additional setup required. The notification uses low priority and is silent.

iOS — ios/[YourApp]/Info.plist

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to detect when you enter or exit defined zones.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Background location access is required for continuous zone monitoring.</string>

<key>NSLocationAlwaysUsageDescription</key>
<string>Background location access is required for continuous zone monitoring.</string>

<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

iOS Background Mode in Xcode:

  1. Open ios/[YourApp].xcworkspace in Xcode
  2. Select the [YourApp] target → Signing & Capabilities
  3. Click + Capability → add Background Modes
  4. Check Location updates

iOS Permission Flow:

iOS requires "Always" location permission for background geofencing:

  1. First Request: When you call requestPermissions({ always: true }), iOS shows a "While in use" permission dialog
  2. Manual Step Required: The user must manually enable "Always" permission in Settings → Privacy & Security → Location Services → Your App → "Always"
  3. Check Permission Status:
const isEnabled = await Polyfence.instance.isLocationServiceEnabled();
if (!isEnabled) {
  // Guide user to enable location services
}

const granted = await Polyfence.instance.requestPermissions({ always: true });
if (granted) {
  // User granted "While in use" — they still need to enable "Always" in Settings
  // You may want to show a dialog guiding them to Settings
}

Quick Start

Step 1: Import and Initialize

import { Polyfence } from 'polyfence-react-native';

await Polyfence.instance.initialize();

Wire onError before the calls below. Several SDK methods (including some used in steps 3 and 5) can emit errors as a side effect of being called. If onError isn't subscribed at the time, those errors are dropped silently — no replay, no warning in the return value. Skip ahead to Step 6, wire onError once, then come back here. The remaining steps assume you've done that.

Want your sessions on your dashboard? Telemetry is anonymous by default — anonymous sessions feed only aggregate stats, not your account's analytics. To attribute them to your Polyfence account, pass your API key (the same one you use for the zone API) to initialize:

await Polyfence.instance.initialize(undefined, { apiKey: YOUR_POLYFENCE_API_KEY });

It's sent as x-api-key with each telemetry upload. React Native has no compile-time env, so supply the key however your app manages config (e.g. react-native-config, an env file, or a constant). To turn telemetry off entirely: initialize(undefined, { disableTelemetry: true }).

Step 2: Request Permissions

iOS: requestPermissions({ always: true }) triggers the system permission dialog.

Android: requestPermissions() does not show a dialog — it only reads the current permission state and returns a boolean. To trigger the OS dialog on Android, use a library like react-native-permissions first, then call requestPermissions() to verify the result.

import { Platform } from 'react-native';
// Android only — trigger the OS permission dialog.
// import { request, PERMISSIONS } from 'react-native-permissions';
// if (Platform.OS === 'android') {
//   await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
//   await request(PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION);
// }

// Both platforms — verify the result. On iOS this ALSO shows the
// system dialog on first call.
const hasPermission = await Polyfence.instance.requestPermissions({ always: true });
if (!hasPermission) {
  // Handle permission denied — e.g. guide the user to Settings.
  return;
}

Step 3: Add Zones

// Circle zone
await Polyfence.instance.addZone({
  id: 'office',
  name: 'Office',
  type: 'circle',
  center: { latitude: 37.422, longitude: -122.084 },
  radius: 150,
});

// Polygon zone
await Polyfence.instance.addZone({
  id: 'campus',
  name: 'Campus',
  type: 'polygon',
  polygon: [
    { latitude: 37.422, longitude: -122.084 },
    { latitude: 37.423, longitude: -122.085 },
    { latitude: 37.424, longitude: -122.083 },
  ],
});

Zones are automatically persisted across app restarts. No hard limit on zone count (tested with 100+ zones on both platforms). Large polygons (1000+ points) are supported; the plugin uses Douglas-Peucker simplification to optimize complex polygons.

Step 4: Listen for Events

const subscription = Polyfence.instance.onGeofenceEvent((event) => {
  switch (event.type) {
    case 'enter':
      console.log(`Entered: ${event.zoneId}`);
      break;
    case 'exit':
      console.log(`Exited: ${event.zoneId}`);
      break;
    case 'dwell':
      console.log(`Stayed in ${event.zoneId} for ${event.dwellDurationMs}ms`);
      break;
    // Recovery events fire ONLY when the tracking service was killed
    // and restarted (Doze kill / OOM / force-stop / phone reboot). On
    // the first GPS fix after restart, the SDK reconciles persisted
    // zone state against the actual location and emits recoveryEnter /
    // recoveryExit for any mismatch. Treat recovery events like
    // enter/exit unless you specifically want to distinguish "user just
    // crossed the boundary" from "user was already inside/outside when
    // the tracking process resumed after being killed."
    case 'recoveryEnter':
      console.log(`Confirmed inside (post-restart): ${event.zoneId}`);
      break;
    case 'recoveryExit':
      console.log(`Confirmed outside (post-restart): ${event.zoneId}`);
      break;
    // Signal-lost / restored fire only when degraded-GPS handling is
    // enabled (gpsStalenessTimeoutMs > 0), and are distinct from recovery
    // events: recoveryEnter/Exit fire when the tracking SERVICE was killed
    // and restarted, whereas signalLost fires while the service is RUNNING
    // but GPS has gone stale. If GPS goes fully stale while you're inside a
    // zone, the SDK reports signalLost — membership is now uncertain, NOT
    // exited — then signalRestored once a valid fix confirms you're still
    // inside (or a normal exit if you actually left). NOTE: on signalLost,
    // event.location is the LAST-KNOWN (stale / approximate) position, not
    // a current fix — don't render it as the user's live location.
    case 'signalLost':
      console.log(`GPS lost, still assumed inside: ${event.zoneId}`);
      break;
    case 'signalRestored':
      console.log(`GPS restored, confirmed inside: ${event.zoneId}`);
      break;
  }
});

// Don't forget to unsubscribe in cleanup
subscription.remove();

ENTER, EXIT, DWELL and RECOVERY events as your device moves

Events fire whether your app is foregrounded, backgrounded, or the screen is locked. Here's what users see on each platform when zones fire in the background:

Background events on iOS lock screen Background events on Android notification shade

Step 5: Start Tracking

await Polyfence.instance.startTracking();

Step 6: Handle Errors (Recommended)

Subscribe to onError before calling any other SDK method. onError is the SDK's central error channel — GPS failures, permission revocations, service issues, battery warnings, zone validation errors, and any other runtime error all route through this one subscription. Methods such as batteryOptimizationStatus(), addZone(), and requestPermissions() can emit errors as a side effect of being called; if no listener is attached at the time, that error is silently dropped — no retry, no replay, no warning in the return value.

const errorSubscription = Polyfence.instance.onError((error) => {
  switch (error.type) {
    case 'gpsPermissionDenied':
      // Guide user to settings
      break;
    case 'gpsServiceDisabled':
      // Prompt to enable GPS
      break;
    default:
      console.log(`Error: ${error.message}`);
  }
});

API Reference

Lifecycle Methods

Method Returns Description
initialize(config?) Promise<void> Initialize the geofencing engine
startTracking() Promise<void> Start background GPS tracking
stopTracking() Promise<void> Stop GPS tracking
dispose() Promise<void> Clean up resources and stop tracking. Safe to re-initialize() afterwards (e.g. logout → login) — re-acquire via Polyfence.instance.
removeAllListeners() void Remove all event listeners without disposing the engine

Zone Management

Method Returns Description
addZone(zone) Promise<void> Add a circle or polygon zone
removeZone(zoneId) Promise<void> Remove a zone by ID
clearAllZones() Promise<void> Remove all zones
getZoneStates() Promise<ZoneState[]> Get current INSIDE/OUTSIDE state for all zones

getZoneStates() only reports reliable inside/outside state after startTracking(). Inside/outside is computed by the running location service, so before tracking starts there is nothing evaluated to report — on Android getZoneStates() returns [] even after addZone(). Always follow this order: initialize()addZone()startTracking()getZoneStates(). (To track membership while running, use the onZoneEnter / onZoneExit events.)

addZone(zone) treats zone.id as the primary key — duplicate IDs silently overwrite. Calling addZone with an id already being monitored replaces the previous zone entry; no error is thrown. Re-adding also resets the persisted INSIDE/OUTSIDE state for that zone (and on iOS, its confidence state). If the device is currently inside the zone, the next reconciliation may fire a fresh enter / recoveryEnter event. In-place metadata edits without a re-enter are a known limitation. If your workflow requires unique IDs, dedupe before calling.

Configuration

Method Returns Description
getConfiguration() Promise<PolyfenceConfiguration> Get current configuration
updateConfiguration(config) Promise<void> Update configuration
resetConfiguration() Promise<void> Reset to defaults
setAccuracyProfile(profile) Promise<void> Set GPS accuracy profile

Permissions & System

Method Returns Description
requestPermissions(options?) Promise<boolean> Request location permissions. Android: reads current state only, does not show a dialog — see Step 2 for the two-step Android flow.
isLocationServiceEnabled() Promise<boolean> Check if location services are enabled
batteryOptimizationStatus() Promise<BatteryOptimizationStatus> Check battery optimization status (Android)
requestBatteryOptimizationExemption() Promise<void> Launch the Android system exemption dialog (fire-and-forget — re-poll batteryOptimizationStatus() to observe the user's response)

Debug & Telemetry

Method Returns Description
debugInfo() Promise<PolyfenceDebugInfo> Get operational diagnostics in 5 groups: systemStatus, performance, battery, zones, recentErrors. For current configuration use getConfiguration(); for current zone membership use getZoneStates()
getSessionTelemetry() Promise<SessionTelemetry> Get session metrics (GPS updates, zone events, battery impact)
errorHistory(options?) Promise<PolyfenceError[]> Get recent errors

Events

Method Callback Description
onLocationUpdate(callback) (location: PolyfenceLocation) => void Raw GPS location updates
onGeofenceEvent(callback) (event: GeofenceEvent) => void Zone enter / exit / dwell / recoveryEnter / recoveryExit / signalLost / signalRestored events
onError(callback) (error: PolyfenceError) => void Central error channel — subscribe before any other SDK call. GPS / permission / service / battery / zone-validation errors all route here; errors fired without a listener are dropped silently
onPerformance(callback) (payload: PerformanceEventPayload) => void Live GPS performance snapshots (type: 'runtime_status') emitted periodically by polyfence-core while tracking. See Performance Events
onHealthScore(callback) (event: HealthScoreEvent) => void Periodic health score (0-100) with top issue
onZoneEnter(callback) (event: GeofenceEvent) => void Zone enter events only
onZoneExit(callback) (event: GeofenceEvent) => void Zone exit events only

All event methods return a Subscription object with a remove() method to unsubscribe.


Events

Geofence Events

Polyfence.instance.onGeofenceEvent((event) => {
  console.log({
    zoneId: event.zoneId,
    zoneName: event.zoneName,
    type: event.type, // 'enter' | 'exit' | 'dwell' | 'recoveryEnter' | 'recoveryExit' | 'signalLost' | 'signalRestored'
    location: event.location, // includes location.activity ('still' | 'walking' | 'running' | 'cycling' | 'driving' | 'unknown')
    timestamp: event.timestamp,
    detectionTimeMs: event.detectionTimeMs, // ms the engine took to detect the transition
    distanceToBoundaryM: event.distanceToBoundaryM, // metres from event location to zone boundary
    dwellDurationMs: event.dwellDurationMs, // only set on DWELL events
  });
});

Location Events

Polyfence.instance.onLocationUpdate((location) => {
  console.log({
    latitude: location.latitude,
    longitude: location.longitude,
    accuracy: location.accuracy, // metres, undefined until the first GPS fix
    speed: location.speed,       // m/s
    activity: location.activity, // 'still' | 'walking' | 'running' | 'cycling' | 'driving' | 'unknown'
    timestamp: location.timestamp,
  });
});

Error Events

Polyfence.instance.onError((error) => {
  console.log({
    type: error.type, // 'gpsPermissionDenied' | 'gpsServiceDisabled' | ...
    message: error.message,
    context: error.context,
    correlationId: error.correlationId,
    timestamp: error.timestamp,
  });
});

Performance Events

onPerformance surfaces live GPS performance snapshots emitted periodically by polyfence-core LocationTracker while tracking is running (typically every ~30 seconds and on strategy / accuracy-profile change). The channel filters to type: 'runtime_status' — health-score events travel on their own onHealthScore subscription, and internal SDK state broadcasts are not surfaced.

const subscription = Polyfence.instance.onPerformance((payload) => {
  // Engine runtime status — emitted periodically (and on change) by
  // polyfence-core LocationTracker. Shape: { type: 'runtime_status',
  // data: { strategy, intervalMs, accuracyProfile, nearestZoneDistanceM,
  //         isStationary, batteryMode, gpsAccuracy, currentGpsAccuracy,
  //         secondsSinceLastGpsFix, gpsAvailabilityDrops5Min, timestamp } }
  const data = payload.data as Record<string, unknown>;
  console.log({
    strategy: data.strategy,                 // 'CONTINUOUS' | 'INTELLIGENT' | ...
    intervalMs: data.intervalMs,
    gpsAccuracy: data.gpsAccuracy,           // metres (current fix)
    currentGpsAccuracy: data.currentGpsAccuracy, // metres (last health-tracked fix; null until first fix)
    nearestZoneDistanceM: data.nearestZoneDistanceM,
  });
});

// Remove the subscription during cleanup (e.g. useEffect return).
subscription.remove();

Configuration

GPS Accuracy Profiles

// Maximum accuracy (highest battery usage)
await Polyfence.instance.setAccuracyProfile('maxAccuracy');

// Balanced accuracy/battery (DEFAULT - recommended)
await Polyfence.instance.setAccuracyProfile('balanced');

// Battery-optimized for background monitoring
await Polyfence.instance.setAccuracyProfile('batteryOptimal');

// Intelligent auto-adjustment
await Polyfence.instance.setAccuracyProfile('adaptive');
Profile Update Interval Battery Impact Use Case
maxAccuracy 5 seconds High Delivery, navigation, fleet tracking
balanced 10 seconds Medium Most location-aware apps (DEFAULT)
batteryOptimal 30 seconds Low Background monitoring, casual use
adaptive Dynamic Variable Apps with varying accuracy needs

GPS profile selector — Max, Balanced, Battery, Smart

Dwell Detection

await Polyfence.instance.updateConfiguration({
  dwellSettings: {
    enabled: true,
    dwellThresholdMs: 5 * 60 * 1000, // 5 minutes
  },
});

Polyfence.instance.onGeofenceEvent((event) => {
  if (event.type === 'dwell') {
    console.log(`User confirmed in ${event.zoneId}`);
  }
});

Zone Clustering

For apps with 100+ zones, clustering improves performance:

await Polyfence.instance.updateConfiguration({
  clusterSettings: {
    enabled: true,
    activeRadiusMeters: 5000, // Check zones within 5km
  },
});

Scheduled Tracking

Track only during specific time windows:

await Polyfence.instance.updateConfiguration({
  scheduleSettings: {
    enabled: true,
    timeWindows: [
      {
        startTime: { hour: 9, minute: 0 },
        endTime: { hour: 17, minute: 0 },
        daysOfWeek: [1, 2, 3, 4, 5], // Monday-Friday
      },
    ],
  },
});

Activity Recognition

Automatically detect activity and optimize GPS:

await Polyfence.instance.updateConfiguration({
  activitySettings: {
    enabled: true,
    // Per-activity update intervals in milliseconds. Defaults are
    // sensible; override only the ones you want to tune.
    stillIntervalMs: 120000,   // 2 min when stationary
    walkingIntervalMs: 15000,  // 15s when walking
    runningIntervalMs: 10000,
    cyclingIntervalMs: 8000,
    drivingIntervalMs: 5000,
  },
});

Additional Permissions Required (Android):

<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

Expo Support

Polyfence requires native modules and cannot run on Expo Go. Use expo-dev-client for a custom development build:

npx create-expo-app MyApp
cd MyApp
npx expo install expo-dev-client
npx expo prebuild --clean
npm install polyfence-react-native
npx expo run:ios    # or run:android

See Expo Custom Development Client docs for more details.


Privacy

Zero PII about your end users. The only personal information Polyfence holds is the developer's account info (email, billing) — same as any paid SaaS.

Different defaults for different data classes:

  • Positions — opt-in. Never persisted on Polyfence servers by default. If you turn retention on, positions are stored in your tenant — never names, phones, emails, or health data.
  • Anonymous telemetry — opt-out, one line disables (see below). Never coordinates, never identifiers, never PII — only aggregates (platform, plugin version, accuracy averages, error counts).
  • Zone events — always on. They're the value we deliver, not surveillance.

This is the deliberate posture, not an inconsistency. See PRIVACY.md for the full breakdown.

Disable telemetry (one line)

await Polyfence.instance.initialize(undefined, { disableTelemetry: true });

Architecture Guarantees

  • On-device geofencing: All zone detection runs locally using native GPS APIs
  • Local persistence: Zones stored in SharedPreferences (Android) / UserDefaults (iOS)
  • No tracking: No user behavior tracking, no cross-app tracking
  • GDPR/CCPA-friendly: Anonymous aggregates only by default, one-line disable for telemetry, opt-in for position retention

Upgrading

From 2.0.x to 2.1.0

Additive release — no public API removed. New surfaces:

  • GeofenceEventType.signalLost / .signalRestored — reported when GPS goes stale while a device is inside a zone. Opt-in via PolyfenceConfiguration.gpsStalenessTimeoutMs (0 = off, the default).

Behavioural change on Android — initialize(config). If you pass any tracking-config key to initialize, the LocationTracker foreground Service now starts as part of initialize rather than being deferred until startTracking(). On Android 8+, context.startService from a backgrounded context throws IllegalStateException — so initialize will now REJECT where it previously silently dropped the config. This is a bug fix (the old behaviour hid misconfigured init calls), but callers that init from a background service must catch the rejection and retry once the app foregrounds. Apps that call initialize on cold-start or from the foreground see no change. iOS is unaffected.

For the full change list, see CHANGELOG.md.

Common Gotchas

Stream Subscription Management

Always remove event subscriptions in cleanup to prevent memory leaks:

useEffect(() => {
  const subscription = Polyfence.instance.onGeofenceEvent((event) => {
    // handle event
  });

  return () => {
    subscription.remove();
  };
}, []);

Zone Persistence

Zones are automatically persisted across app restarts — no manual persistence needed. When loading zones from an external source, consider delta-based sync to avoid re-registering all zones.

OEM Battery Restrictions (Android)

Some Android manufacturers aggressively kill background services. If tracking stops, the user likely needs to whitelist your app. See dontkillmyapp.com for device-specific instructions.

NativeModule Not Found

If you see "NativeModule not found" error:

  1. Rebuild the app so autolinking picks up the module (RN 0.60+ autolinks — react-native link is obsolete); on iOS run cd ios && pod install then rebuild
  2. For Expo: Use expo-dev-client (not Expo Go)
  3. Clear build caches: rm -rf node_modules && npm install && cd ios && rm -rf Pods && pod install

Pod Install Fails (iOS)

cd ios
rm -rf Pods Podfile.lock
pod repo update
pod install

Android Build Fails

Ensure minimum SDK is 24+:

android {
    defaultConfig {
        minSdkVersion 24
    }
}

Known Differences from Flutter

The following Flutter APIs are intentionally deferred from this package:

  • enableIntelligentOptimization(), enableProximityOptimization(), enableMovementOptimization() — ML-powered optimization APIs. These will be added when the intelligence layer is integrated (planned for a future release).
  • zones getter — Use getZoneStates() to query current zone state.
  • currentConfiguration getter — Use getConfiguration() instead.
  • statusStream — Use onPerformance() event listener for runtime status updates.

These gaps will be addressed in subsequent releases.


Troubleshooting

Debugging

Android — Filter logcat:

adb logcat | grep -E "LocationTracker|GeofenceEngine|Polyfence"

iOS — Filter Xcode console:

LocationTracker GeofenceEngine Polyfence

Programmatic debugging — Use the debug API:

const debug = await Polyfence.instance.debugInfo();

// systemStatus — permissions, GPS, last fix
console.log('Last fix:', debug.systemStatus.lastLocationUpdate); // ms since epoch, 0 if no fix yet
console.log('GPS enabled:', debug.systemStatus.isGpsEnabled);
console.log('Permission granted:', debug.systemStatus.isLocationPermissionGranted);

// zones — counts (use getZoneStates() for inside/outside membership)
console.log('Active zones:', debug.zones.activeZones);

// performance / battery
console.log('Detections:', debug.performance.totalZoneDetections);
console.log('Battery:', debug.battery.batteryLevel, debug.battery.isCharging ? '⚡' : '');

// recent errors (already normalized to PolyfenceError shape)
debug.recentErrors.forEach((err) => console.log(err.type, err.message));

Note: debugInfo() is for operational diagnostics. For the current configuration (accuracy profile, update strategy, intervals) call getConfiguration(). For the current zone membership (which zones the user is inside right now) call getZoneStates() or subscribe to onZoneEnter / onZoneExit.

Reporting Issues

When opening a GitHub issue, include:

  1. Output of Polyfence.instance.debugInfo()
  2. Device manufacturer and OS version (e.g., Samsung Galaxy S24, Android 14)
  3. Whether battery optimization is disabled
  4. Logcat/Xcode console output
  5. Minimal code sample to reproduce

Contributing

Contributions are welcome. See CONTRIBUTING.md for development setup, code style, and PR guidelines.

Support

License

MIT — see LICENSE

Copyright (c) 2026 Polyfence

About

React Native bridge for the Polyfence geofence layer — privacy-first polygon and circle geofencing on-device. Same zones run on mobile, IoT, and server.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages