npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

esri-react-native

v1.0.1

Published

React Native Expo module for ArcGIS Maps SDK with map visualization and location services

Downloads

351

Readme

esri-react-native

React Native Expo module for ArcGIS Maps SDK with map visualization, location services, and comprehensive observability features.

Features

  • 🗺️ ArcGIS Maps Integration: Full-featured mapping with ArcGIS Maps SDK for iOS and Android
  • 📍 Markers & Interaction: Interactive markers with drag support and tap events
  • 🏔️ Vector Tiles: Support for custom vector tile layers (KOOP, custom services)
  • 📍 Location Services: User location display with various auto-pan modes
  • 🔐 Authentication: Support for ArcGIS API keys and custom Bearer tokens
  • 🐛 Observability: Advanced structured logging (native + JS) with normalization, deduplication, scope, sequence IDs, memory usage (iOS) and correlation hooks.
  • ⚡ Performance: Optimized rendering and thread-safe operations
  • 🛡️ Defensive: Comprehensive input validation and graceful error handling

Architecture Overview

Both native layers use a thin EsriReactNativeView (under 250 lines) that only orchestrates, splitting responsibilities into dedicated packages (full tables in API_REFERENCE.md).

Android — android/src/main/java/expo/modules/esrireactnative/:

  • core/ — pure Kotlin (no android.*/com.arcgismaps.* imports): Validation, ColorParser, FlyToPlanner, VectorTileSpec, MarkerSpec, BasemapSpec, HostAllowlist, Geodesy, EventPayloads, ZoomScale.
  • controllers/ — SDK-facing adapters, one responsibility each, receiving a logger and coroutine scope by constructor: MapHostController, BasemapController, ScaleLimitsController, VectorTileController, MarkerController, NavigationController, InitialViewpointController, LocationController.
  • geo/ — GeometryUtils (envelope operations, Haversine fallback; the only controller-adjacent code allowed to touch com.arcgismaps.*).
  • networking/ / logging/ — HttpAuth/AllowedHostsAuthInterceptor (selective Bearer token injection, HTTP client configured once per process) and NativeLogger (per-view, no singleton).

iOS — ios/, mirroring the same split in Swift:

  • Core/ — pure Swift (no import ArcGIS/UIKit/ExpoModulesCore): Validation, ColorParser, FlyToPlanner, VectorTileSpec, MarkerSpec, BasemapSpec, HostAllowlist, EventPayloads, ZoomScale, and the generated Version.swift.
  • Controllers/ — SDK-facing adapters (@MainActor), one responsibility each, receiving an EsriLogger by constructor: MapHostController, ScaleLimitsController, BasemapController, VectorTileController, MarkerController, NavigationController, InitialViewpointController, LocationController.
  • Networking/HttpAuth.swift — AuthState (lock-protected Bearer token + allowlist) and AllowedHostsAuthInterceptor, assigned once per process to ArcGISURLSession.dataTaskInterceptor/downloadTaskInterceptor (the SDK's official interception mechanism, mirroring Android's Interceptor) via EsriHttpSetup.ensureConfigured().
  • SwiftUI/MapContainerView.swift — the ArcGIS.MapView wrapper (gestures, viewpoint tracking), with the logger injected.
  • Logging/NativeLogger.swift — per-view, no singleton.

This keeps responsibilities isolated and maintainable without changing the public JS API.

Testing

JUnit unit tests (android/src/test/java/.../core/) cover every core/ file against fixtures shared with JS and Swift (test-fixtures/*.json): validation, color parsing, flyTo duration/curve math, vector tile spec/signature, marker spec, basemap spec, host allowlist, geodesy. Run them with npm run test:android (via example/android, see CLAUDE.md for the fixture-as-Gradle-input gotcha).

swift test (npm run test:swift, requires a full Xcode install — Command Line Tools alone can't link XCTest) covers the same shared fixtures against ios/Core/ (ios/Tests/CoreTests/), plus iOS-only cases (finalURL, MarkerDiff, EventPayloads, noStateDuration, intermediateScale) documented as not fixture-driven where iOS behavior intentionally diverges from Android (see docs/sdk-notes.md).

Behavior coupled to the ArcGIS runtime (controllers) is validated by compiling and via the manual parity checklist (docs/parity-checklist.md) in the example app and in downstream consumers.

Installation

npm install esri-react-native

Requirements

  • Expo SDK 54+
  • React Native 0.81+
  • iOS 17.0+
  • Android API 28+
  • Xcode (full install, not just Command Line Tools) on first install, to vendor the ArcGIS xcframeworks

Config Plugin

Add the plugin to your Expo config (app.json/app.config.ts):

{
  "expo": {
    "plugins": [
      [
        "esri-react-native",
        {
          "allowedHosts": ["myserver.example.com"]
        }
      ]
    ]
  }
}

Options

| Option | Type | Default | Description | | -------------------------------- | ---------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | iosDeploymentTarget | string | "17.0" | Floor for the iOS deployment target (Podfile.properties.json and every pbxproj build configuration). Only raises the current value, never lowers it. | | androidMinSdkVersion | number | 28 | Floor for android.minSdkVersion in gradle.properties. Same floor-only behavior as iosDeploymentTarget. | | addLocationPermissions | boolean | true | Adds NSLocation*UsageDescription (iOS) and ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION (Android) when not already present. | | allowedHosts | string[] | [] | Hosts, or full URLs, allowed to receive the Authorization: Bearer header. Normalized to lowercase hostnames. | | deriveAllowedHostFromExtra | boolean | true | Also derives an allowed host from expo.extra.koopServerUrl, when present. | | addATSExceptionForAllowedHosts | boolean | true | Adds an App Transport Security exception (plain HTTP) for allowedHosts, scoped to atsExceptionEnvs. | | atsExceptionEnvs | AppEnv[] | ["development"] | Environments where the ATS exception is applied. AppEnv is "development" \| "preview" \| "production". | | env | AppEnv | process.env.APP_ENV → process.env.EAS_BUILD_PROFILE → "development" | Overrides the environment used to resolve atsExceptionEnvs. |

What it generates

  • iOS: NSLocation*UsageDescription entries, EsriAllowedHosts in Info.plist, ATS exceptions for allowedHosts (only in the envs listed in atsExceptionEnvs), and a floor on IPHONEOS_DEPLOYMENT_TARGET / ios.deploymentTarget.
  • Android: ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION permissions, the Esri Maven repository in the root build.gradle, and a floor on android.minSdkVersion in gradle.properties.
  • expo.extra.esriAllowedHosts with the resolved, normalized host list, for consumption from JS.

Vendoring the ArcGIS SDK

The ArcGIS xcframeworks aren't published on npm. A postinstall script (npm run ios:vendor) downloads and unpacks them into ios/Vendor on first install. It's idempotent — tracked by an ios/Vendor/.version marker, so it's skipped once the expected version is already there — and requires a full Xcode install (not just Command Line Tools); without Xcode it warns instead of failing. If ios/Vendor is still missing when you build, EsriReactNative.podspec raises a clear error rather than failing with an obscure linker error — re-run npm run ios:vendor on a machine with Xcode, or copy ios/Vendor from one that already has it.

Usage

Basic Map

import React from "react";
import { StyleSheet } from "react-native";
import { EsriReactNativeView } from "esri-react-native";

export default function App() {
  return (
    <EsriReactNativeView
      style={styles.map}
      apiKey="YOUR_ARCGIS_API_KEY"
      showUserLocation={true}
      autoPanMode="navigation"
      initialCenter={{ latitude: 40.7128, longitude: -74.006 }}
      initialScale={50000}
      vectorTiles={[
        {
          id: "osm-vector",
          url: "https://basemaps.arcgis.com/arcgis/rest/services/OpenStreetMap_v2/VectorTileServer",
          reference: false,
        },
      ]}
    />
  );
}
          ```

          ### Licensing

          ArcGIS Runtime requires both an API key (for basemaps/services) and optionally a license key (to exit Developer mode and unlock additional capabilities). If you do not provide a `licenseKey` the app runs in Developer mode.

          Minimal usage:

          ```tsx
          <EsriReactNativeView apiKey={"YOUR_API_KEY"} licenseKey={"runtimelite,1000,..."} />
          ```

          The license string format matches the ArcGIS documentation. Invalid or blank values are ignored with a structured error event.

const styles = StyleSheet.create({
  map: {
    flex: 1,
  },
});

With Interactive Markers

Logging & Observability

The library emits structured log events you can intercept via the onLog prop. Every log passes through a normalizer so that both native (iOS/Android) and JS logs share the same shape.

import { LogEntry } from "esri-react-native";

function handleLog(entry: LogEntry) {
  // Send to Sentry, Datadog, etc.
  if (entry.level === "error") {
    console.error("[MapError]", entry);
  }
}

<EsriReactNativeView onLog={handleLog} debugLogs />

LogEntry Fields

| Field | Type | Description | | ---------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------- | | level | debug \| info \| warn \| error | Normalized severity. Non‑standard domain levels (INIT, ESRI, ZOOM…) are mapped to info with originalLevel. | | message | string | Human readable message (never empty, fallback '(no message)'). | | detail | string? | Additional context or error details. | | where | string? | Code location (e.g. EsriReactNativeView.goTo). | | scope | string? | High‑level domain extracted from where or original level (e.g. ZOOM, INIT). | | platform | ios \| android | Origin platform (guessed in JS if missing). | | ts | number | Unix seconds timestamp. | | thread | string? | main or background (native only). | | sequence | number? | Monotonic counter (per process). Useful for ordering. | | originalLevel | string? | Raw level before mapping (e.g. ZOOM). | | memoryMB | number? | Approximate resident memory (iOS). | | version | string? | Native bundle version (iOS). | | operationId | string? | Correlation id for future grouped operations (flyTo/goTo). | | duplicateCount | number | Number of times the same message repeated in a short window (starts at 1). |

Deduplication

Rapid identical messages within a 1.5s window increment duplicateCount instead of flooding output. After a small interval the count resets automatically. Deduplication is automatic and scoped per view instance; there is no public API to reset it manually.

Error Handling

Errors use a separate onError callback with fields: code, message, detail?, where?, ts, thread?, originalErrorMessage?.

Recommended Consumption Pattern

  1. Filter by level for routing (e.g., send only error/warn to external monitoring).
  2. Use scope to build dashboards (performance of ZOOM, frequency of MARKER).
  3. Collapse bursts by looking at duplicateCount.

Operation Correlation (operationId roadmap)

Upcoming enhancement: navigation methods (goTo, multi‑phase flyTo) and related map interactions will emit a generated operationId that stays constant across all log entries and intermediate viewpoint changes for that operation. This enables:

  • Grouping performance metrics (start → end duration)
  • Attaching analytics/events to a single user intent
  • Multi-platform tracing (native + JS)

Until released, placeholder field may be null or omitted. Code relying on it should degrade gracefully. 4. Correlate sequences or future operationId for multi‑phase animations (flyTo).

Debug Tips

| Symptom | Cause | Fix | | -------------------------- | ------------------------------------------- | -------------------------------------------------------- | | Many duplicateCount > 10 | Tight loop / redundant state updates | Throttle calling code or suppress identical triggers. | | Missing scope | Location lacks a prefix or non‑domain level | Add meaningful where parameter in native/js log calls. | | Platform shows wrong value | JS fallback heuristics fired | Ensure native emission includes platform. |

Enabling Debug Logs

Set the debugLogs prop to true; debug messages (internals, clamp decisions, LOD readiness) appear only when enabled.

<EsriReactNativeView debugLogs onLog={handleLog} />

Redaction & Truncation

Sensitive values (API keys, tokens) are redacted natively showing only a short prefix.

import React, { useState, useCallback } from "react";
import {
  EsriReactNativeView,
  MarkerData,
  MapTapEvent,
  MarkerDragEvent,
} from "esri-react-native";

export default function InteractiveMap() {
  const [markers, setMarkers] = useState<MarkerData[]>([
    {
      id: "marker-1",
      coordinate: { latitude: 40.7128, longitude: -74.006 },
      color: "red",
      draggable: true,
      title: "Draggable Marker",
    },
    {
      id: "marker-2",
      coordinate: { latitude: 40.7589, longitude: -73.9851 },
      color: "blue",
      draggable: false,
      title: "Fixed Marker",
    },
  ]);

  const handleMapTap = useCallback((event: MapTapEvent) => {
    console.log("Tapped at:", event.coordinate);
    // Add new marker at tapped location
    const newMarker: MarkerData = {
      id: `marker-${Date.now()}`,
      coordinate: event.coordinate,
      color: "green",
      draggable: true,
    };
    setMarkers((prev) => [...prev, newMarker]);
  }, []);

  const handleMarkerDrag = useCallback((event: MarkerDragEvent) => {
    console.log("Marker dragged:", event.marker);
    // Update marker position in state
    setMarkers((prev) =>
      prev.map((marker) =>
        marker.id === event.marker.id ? event.marker : marker,
      ),
    );
  }, []);

  return (
    <EsriReactNativeView
      style={{ flex: 1 }}
      apiKey="YOUR_ARCGIS_API_KEY"
      markers={markers}
      onMapTap={handleMapTap}
      onMarkerDragEnd={handleMarkerDrag}
      initialCenter={{ latitude: 40.7128, longitude: -74.006 }}
      initialScale={50000}
    />
  );
}

Props

| Prop | Type | Description | | ------------------ | --------------------------------------- | -------------------------------------- | | apiKey | string | ArcGIS API key for authentication | | bearerToken | string | Bearer token for custom authentication | | showUserLocation | boolean | Show user's current location on map | | autoPanMode | 'off' \| 'recenter' \| 'navigation' | Auto-pan behavior for location | | initialCenter | {latitude: number, longitude: number} | Initial map center coordinates | | initialScale | number | Initial map scale (zoom level) | | minZoomLevel | number | Minimum zoom level (Mapbox style) | | maxZoomLevel | number | Maximum zoom level (Mapbox style) | | vectorTiles | VectorTile[] | Array of vector tile layer definitions | | allowedHosts | string[] | Allowed hosts for network requests | | markers | MarkerData[] | Array of markers to display on map | | onMapTap | (event: MapTapEvent) => void | Callback when map is tapped | | onMarkerDragEnd | (event: MarkerDragEvent) => void | Callback when marker is dragged |

Type Definitions

interface VectorTile {
  id: string;
  url: string;
  minZoom?: number;
  maxZoom?: number;
  reference?: boolean;
  params?: Record<string, string>;
}

interface Coordinate {
  latitude: number;
  longitude: number;
}

interface MarkerData {
  id: string;
  coordinate: Coordinate;
  color?: string; // Hex color or CSS color name
  draggable?: boolean;
  title?: string;
  subtitle?: string;
}

interface MapTapEvent {
  coordinate: Coordinate;
}

interface MarkerDragEvent {
  marker: MarkerData; // Updated marker with new coordinates
}

Documentation

  • API_REFERENCE.md: props, events, imperative handle, error codes.
  • docs/contracts.md: frozen cross-platform contracts (defaults, event payloads, error codes, Bearer rules).
  • CHANGELOG.md: release notes (Keep a Changelog).
  • docs/sdk-notes.md: every ArcGIS SDK API the library relies on, with its verified signature.
  • docs/parity-checklist.md: manual checks used to validate native changes on iOS and Android.

Contributing

See CONTRIBUTING.md for the local setup, the project rules (pure core/ logic with shared fixtures, thin native views, verified SDK APIs) and the pull request checklist. This project follows the Code of Conduct.

Security

Report vulnerabilities privately as described in SECURITY.md.

License

MIT © 2025-2026 Victor Corral. The ArcGIS Maps SDK is licensed separately by Esri; you need your own API key and, optionally, a license string.