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

@jindun619/react-native-amap

v2.0.0

Published

React Native bridge for Amap ios/android sdk.

Readme

react-native-amap

npm version License: MIT

React Native bridge for AMap (高德地图) iOS/Android SDK with full New Architecture (Fabric) support and built-in Expo config plugin.


Table of Contents


Features

  • New Architecture Ready - Full support for React Native 0.81+ with Fabric and TurboModules
  • 🎯 Expo Config Plugin - Zero-config setup for Expo managed workflow
  • 🗺️ Map Display - Standard and satellite map types with full gesture controls
  • 📍 Markers - Add, remove, and customize markers with callouts
  • 🎨 Custom Icons - Support for custom marker icons from URLs or local assets
  • 🔢 Marker Clustering - Automatic marker clustering with customizable appearance
  • 📐 Overlays - Polylines, polygons, and circles with customizable styles
  • 📱 User Location - Show user's current location on the map
  • 🎯 Camera Control - Programmatic camera positioning with smooth animations
  • 📡 Events - Rich event system for map interactions

Installation

Expo Managed Workflow (Recommended)

The easiest way to use this library in Expo projects - just 2 steps!

Step 1: Install the package

npx expo install @jindun619/react-native-amap

The config plugin will be automatically detected by Expo CLI!

Step 2: Add your API keys

Option A: Using environment variables (Recommended)

Create a .env file in your project root:

# .env (add this file to .gitignore!)
EXPO_PUBLIC_AMAP_IOS_API_KEY=your_ios_api_key_here
EXPO_PUBLIC_AMAP_ANDROID_API_KEY=your_android_api_key_here

Option B: Using app.config.js

// app.config.js
export default {
  expo: {
    // ... other config
    plugins: [
      [
        '@jindun619/react-native-amap',
        {
          iosApiKey: 'your_ios_api_key',
          androidApiKey: 'your_android_api_key',
          // Optional: custom permission descriptions
          iosLocationWhenInUseDescription: 'We need your location to show your position on the map',
          iosLocationAlwaysDescription: 'We need your location to show your position on the map'
        }
      ]
    ]
  }
};

Step 3: Rebuild your app

# Prebuild to apply native changes
npx expo prebuild

# Run on your device/simulator
npx expo run:ios
# or
npx expo run:android

That's it! 🎉 The config plugin automatically handles:

  • ✅ iOS Info.plist configuration (API key, location permissions, App Transport Security)
  • ✅ iOS AppDelegate initialization (AMap SDK privacy compliance)
  • ✅ Android AndroidManifest.xml setup (API key, permissions)
  • ✅ Android build.gradle Maven repository configuration

Bare React Native

Step 1: Install the package

npm install @jindun619/react-native-amap
# or
yarn add @jindun619/react-native-amap

Step 2: iOS Setup

  1. Install pods:
cd ios && pod install
  1. Add AMap API key to Info.plist:
<key>AMapApiKey</key>
<string>YOUR_AMAP_IOS_API_KEY</string>
  1. Add location permissions to Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show your position on the map</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to show your position on the map</string>
  1. Add App Transport Security exception to Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>amap.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>
  1. ⚠️ CRITICAL: Initialize AMap SDK in AppDelegate

Without this step, the map will not display!

For Objective-C (AppDelegate.mm):

#import <AMapFoundationKit/AMapFoundationKit.h>
#import <MAMapKit/MAMapKit.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // Configure AMap SDK privacy compliance (MUST be called before MAMapView instantiation)
  [MAMapView updatePrivacyShow:AMapPrivacyShowStatusDidShow privacyInfo:AMapPrivacyInfoStatusDidContain];
  [MAMapView updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
  [[AMapServices sharedServices] setEnableHTTPS:YES];

  // ... rest of your AppDelegate code
  return YES;
}

For Swift (AppDelegate.swift):

import AMapFoundationKit
import MAMapKit

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
  // Configure AMap SDK privacy compliance
  MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
  MAMapView.updatePrivacyAgree(.didAgree)
  AMapServices.shared().enableHTTPS = true

  // ... rest of your AppDelegate code
  return true
}

Step 3: Android Setup

  1. Add AMap Maven repository to android/build.gradle:
allprojects {
  repositories {
    // ... other repositories
    maven { url "https://maven.aliyun.com/repository/public" }
  }
}
  1. Add permissions and API key to AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- Required permissions -->
  <uses-permission android:name="android.permission.INTERNET" />
  <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_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

  <application>
    <!-- AMap API Key -->
    <meta-data
      android:name="com.amap.api.v2.apikey"
      android:value="YOUR_AMAP_ANDROID_API_KEY" />

    <!-- ... rest of your application config -->
  </application>
</manifest>

Getting Your API Keys

You need separate API keys for iOS and Android from AMap:

  1. Go to AMap Developer Console
  2. Create an account or sign in
  3. Create a new application
  4. Get your iOS and Android API keys (they are different!)

Important:

  • iOS and Android require separate API keys
  • Keep your API keys secure (use .env file and add it to .gitignore)

Quick Start

Basic Example

import React, { useRef } from 'react';
import { StyleSheet, View } from 'react-native';
import { AmapView, type AmapViewHandle } from '@jindun619/react-native-amap';

export default function App() {
  const mapRef = useRef<AmapViewHandle>(null);

  return (
    <View style={styles.container}>
      <AmapView
        ref={mapRef}
        style={styles.map}
        mapType="standard"
        showsUserLocation={true}
        onMapReady={() => console.log('Map is ready!')}
        onMapPress={(event) => console.log('Map pressed:', event.coordinate)}
      />
    </View>
  );
}

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

Adding Markers

import { useEffect, useRef } from 'react';
import { AmapView, type AmapViewHandle } from '@jindun619/react-native-amap';

export default function MapWithMarkers() {
  const mapRef = useRef<AmapViewHandle>(null);

  useEffect(() => {
    // Add marker when map is ready
    const addMarker = async () => {
      await mapRef.current?.addMarker({
        id: 'beijing',
        coordinate: { latitude: 39.9042, longitude: 116.4074 },
        title: 'Beijing',
        subtitle: 'Capital of China',
        showsCallout: true,
      });

      // Animate camera to marker
      await mapRef.current?.animateToRegion(39.9042, 116.4074, 15, 1000);
    };

    addMarker();
  }, []);

  return (
    <AmapView
      ref={mapRef}
      style={{ flex: 1 }}
      showsUserLocation={true}
      onMarkerPress={(event) => console.log('Marker pressed:', event.id)}
    />
  );
}

Configuration

Expo Config Plugin Options

When using Expo, you can configure the plugin in app.config.js:

export default {
  expo: {
    plugins: [
      [
        '@jindun619/react-native-amap',
        {
          // Required: API keys
          iosApiKey: 'your_ios_key',           // or use EXPO_PUBLIC_AMAP_IOS_API_KEY env var
          androidApiKey: 'your_android_key',   // or use EXPO_PUBLIC_AMAP_ANDROID_API_KEY env var

          // Optional: Custom permission descriptions
          iosLocationWhenInUseDescription: 'Custom description for location permission',
          iosLocationAlwaysDescription: 'Custom description for always location permission',
        }
      ]
    ]
  }
};

Environment Variables

For better security, use environment variables:

# .env
EXPO_PUBLIC_AMAP_IOS_API_KEY=your_ios_key
EXPO_PUBLIC_AMAP_ANDROID_API_KEY=your_android_key

The plugin will automatically read these variables if API keys are not provided in the config.


API Reference

Props

Map Configuration

| Prop | Type | Default | Description | |------|------|---------|-------------| | mapType | 'standard' \| 'satellite' | 'standard' | Map display type | | showsBuildings | boolean | true | Show 3D buildings | | showsTraffic | boolean | false | Show traffic layer | | showsLabels | boolean | true | Show text labels | | showsUserLocation | boolean | false | Show user's location |

Interaction Controls

| Prop | Type | Default | Description | |------|------|---------|-------------| | zoomEnabled | boolean | true | Enable pinch to zoom | | scrollEnabled | boolean | true | Enable pan gestures | | rotateEnabled | boolean | true | Enable rotation gestures | | tiltEnabled | boolean | true | Enable 3D tilt gestures |

Marker Features

| Prop | Type | Default | Description | |------|------|---------|-------------| | clusteringEnabled | boolean | false | Enable automatic marker clustering |


Methods

Access these methods via ref:

const mapRef = useRef<AmapViewHandle>(null);

Camera Control

// Animate to a specific location
await mapRef.current?.animateToRegion(
  latitude: number,
  longitude: number,
  zoom: number,
  duration?: number  // milliseconds (optional)
);

// Set camera position (no animation)
await mapRef.current?.setCamera({
  latitude: number,
  longitude: number,
  zoom: number,
  tilt?: number,      // 0-60 degrees (optional)
  rotation?: number   // 0-360 degrees (optional)
});

Marker Management

// Add a marker
await mapRef.current?.addMarker({
  id: string,
  coordinate: { latitude: number, longitude: number },
  title?: string,
  subtitle?: string,
  showsCallout?: boolean,
  icon?: string  // URL, asset name, or require()
});

// Remove a marker
await mapRef.current?.removeMarker(id: string);

// Clear all markers
await mapRef.current?.clearMarkers();

// Show/hide marker callout
await mapRef.current?.showCallout(id: string);
await mapRef.current?.hideCallout(id: string);

Overlays

Polyline:

await mapRef.current?.addPolyline({
  id: string,
  coordinates: Array<{ latitude: number, longitude: number }>,
  strokeColor?: string,  // hex color (e.g., '#FF0000')
  strokeWidth?: number   // in pixels
});

await mapRef.current?.removePolyline(id: string);
await mapRef.current?.clearPolylines();

Polygon:

await mapRef.current?.addPolygon({
  id: string,
  coordinates: Array<{ latitude: number, longitude: number }>,
  strokeColor?: string,
  strokeWidth?: number,
  fillColor?: string  // hex color with alpha (e.g., '#FF000080')
});

await mapRef.current?.removePolygon(id: string);
await mapRef.current?.clearPolygons();

Circle:

await mapRef.current?.addCircle({
  id: string,
  center: { latitude: number, longitude: number },
  radius: number,  // in meters
  strokeColor?: string,
  strokeWidth?: number,
  fillColor?: string
});

await mapRef.current?.removeCircle(id: string);
await mapRef.current?.clearCircles();

Events

| Event | Payload | Description | |-------|---------|-------------| | onMapReady | void | Called when map is loaded and ready | | onRegionChange | RegionChangeEvent | Called when visible region changes | | onMapPress | MapPressEvent | Called when map is tapped | | onMapLongPress | MapPressEvent | Called when map is long pressed | | onMarkerPress | MarkerPressEvent | Called when marker is tapped | | onClusterPress | ClusterPressEvent | Called when marker cluster is tapped |


Types

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

interface Marker {
  id: string;
  coordinate: LatLng;
  title?: string;
  subtitle?: string;
  showsCallout?: boolean;
  icon?: string | number;
}

interface CameraPosition {
  latitude: number;
  longitude: number;
  zoom: number;
  tilt?: number;      // 0-60 degrees
  rotation?: number;  // 0-360 degrees
}

interface MapPressEvent {
  coordinate: LatLng;
}

interface MarkerPressEvent {
  id: string;
  coordinate: LatLng;
}

interface RegionChangeEvent {
  latitude: number;
  longitude: number;
  latitudeDelta: number;
  longitudeDelta: number;
  zoom: number;
}

interface ClusterPressEvent {
  coordinate: LatLng;
  markerCount: number;
}

interface Polyline {
  id: string;
  coordinates: LatLng[];
  strokeColor?: string;
  strokeWidth?: number;
}

interface Polygon {
  id: string;
  coordinates: LatLng[];
  strokeColor?: string;
  strokeWidth?: number;
  fillColor?: string;
}

interface Circle {
  id: string;
  center: LatLng;
  radius: number;  // in meters
  strokeColor?: string;
  strokeWidth?: number;
  fillColor?: string;
}

Examples

Complete Example with All Features

import React, { useRef, useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import { AmapView, type AmapViewHandle } from '@jindun619/react-native-amap';

export default function App() {
  const mapRef = useRef<AmapViewHandle>(null);
  const [clusteringEnabled, setClusteringEnabled] = useState(false);
  const [markerCount, setMarkerCount] = useState(0);

  const handleAddMarker = async () => {
    const id = `marker-${markerCount}`;
    await mapRef.current?.addMarker({
      id,
      coordinate: {
        latitude: 39.9042 + (Math.random() - 0.5) * 0.1,
        longitude: 116.4074 + (Math.random() - 0.5) * 0.1
      },
      title: `Marker ${markerCount + 1}`,
      subtitle: `ID: ${id}`,
      showsCallout: true,
    });
    setMarkerCount(markerCount + 1);
  };

  const handleAddPolyline = async () => {
    await mapRef.current?.addPolyline({
      id: 'route-1',
      coordinates: [
        { latitude: 39.9042, longitude: 116.4074 },
        { latitude: 39.9142, longitude: 116.4174 },
        { latitude: 39.9242, longitude: 116.4274 }
      ],
      strokeColor: '#FF0000',
      strokeWidth: 5
    });
  };

  const handleAddCircle = async () => {
    await mapRef.current?.addCircle({
      id: 'area-1',
      center: { latitude: 39.9042, longitude: 116.4074 },
      radius: 1000,
      strokeColor: '#0000FF',
      strokeWidth: 2,
      fillColor: '#0000FF40'
    });
  };

  const handleGoToBeijing = async () => {
    await mapRef.current?.animateToRegion(39.9042, 116.4074, 12, 1000);
  };

  return (
    <View style={styles.container}>
      <AmapView
        ref={mapRef}
        style={styles.map}
        mapType="standard"
        showsUserLocation={true}
        showsTraffic={false}
        clusteringEnabled={clusteringEnabled}
        onMapReady={() => console.log('Map ready!')}
        onMapPress={(event) => console.log('Map pressed:', event.coordinate)}
        onMarkerPress={(event) => console.log('Marker pressed:', event.id)}
        onClusterPress={(event) =>
          console.log(`Cluster: ${event.markerCount} markers`)
        }
        onRegionChange={(event) => console.log('Region changed:', event)}
      />

      <View style={styles.controls}>
        <Button title="Add Marker" onPress={handleAddMarker} />
        <Button title="Add Polyline" onPress={handleAddPolyline} />
        <Button title="Add Circle" onPress={handleAddCircle} />
        <Button title="Go to Beijing" onPress={handleGoToBeijing} />
        <Button
          title={clusteringEnabled ? 'Disable Clustering' : 'Enable Clustering'}
          onPress={() => setClusteringEnabled(!clusteringEnabled)}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  map: { flex: 1 },
  controls: {
    position: 'absolute',
    bottom: 20,
    left: 20,
    right: 20,
    gap: 10,
  },
});

Custom Marker Icons

// From URL
await mapRef.current?.addMarker({
  id: 'marker-url',
  coordinate: { latitude: 39.9042, longitude: 116.4074 },
  icon: 'https://example.com/marker-icon.png'
});

// From local asset (iOS: Assets.xcassets, Android: drawable)
await mapRef.current?.addMarker({
  id: 'marker-asset',
  coordinate: { latitude: 39.9042, longitude: 116.4074 },
  icon: 'marker_icon'  // asset name without extension
});

Marker Clustering

<AmapView
  ref={mapRef}
  clusteringEnabled={true}
  onClusterPress={(event) => {
    console.log(`Cluster with ${event.markerCount} markers`);
    console.log(`Location: ${event.coordinate.latitude}, ${event.coordinate.longitude}`);
  }}
/>

Clustering features:

  • Automatic grouping of markers within 60 pixels
  • Purple cluster markers with marker count
  • Custom onClusterPress event
  • Re-clusters automatically when map moves

Troubleshooting

Map is not displaying

Most common issue: Missing AMap SDK initialization in AppDelegate (iOS)

Make sure you added the privacy compliance and HTTPS configuration in AppDelegate.mm or AppDelegate.swift:

MAMapView.updatePrivacyShow(.didShow, privacyInfo: .didContain)
MAMapView.updatePrivacyAgree(.didAgree)
AMapServices.shared().enableHTTPS = true

For Expo users: The config plugin should handle this automatically. If the map still doesn't display:

  1. Run npx expo prebuild --clean to regenerate native code
  2. Rebuild your development build
  3. Check that your API keys are correctly set in .env or app.config.js

Other common issues

1. Invalid API Key

  • Make sure you're using the correct API key for iOS/Android
  • iOS and Android keys are different - don't mix them up!
  • Verify keys at AMap Developer Console

2. Missing permissions (Android)

  • Ensure all required permissions are added to AndroidManifest.xml
  • For Expo: plugin adds permissions automatically

3. Missing Maven repository (Android)

  • Android needs the AMap Maven repository in build.gradle
  • For Expo: plugin adds repository automatically

4. Pod install fails (iOS)

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

5. Clean rebuild

# iOS
cd ios && rm -rf Pods Podfile.lock && pod install && cd ..

# Android
cd android && ./gradlew clean && cd ..

# Expo
npx expo prebuild --clean

Map displays but crashes on interaction

This is usually due to missing privacy compliance setup on iOS. Make sure the privacy methods are called before any map view is created.

Location not showing

  1. Check that location permissions are granted
  2. Verify showsUserLocation={true} prop is set
  3. On iOS, ensure Info.plist has location usage descriptions
  4. Test on a real device (simulators may not have location)

Expo: "Module not found" error

# Clear cache and restart
npx expo start --clear

# Or reinstall
rm -rf node_modules
npm install
npx expo prebuild --clean

Build errors after upgrading

# Clean everything and rebuild
yarn clean
rm -rf node_modules yarn.lock
yarn install
npx expo prebuild --clean

Requirements

  • React Native >= 0.81.0 (New Architecture)
  • iOS >= 12.0
  • Android >= API 21
  • AMap API Keys (separate for iOS and Android)

For Expo:

  • Expo >= 54.0.0
  • Development Build or Standalone Build (not Expo Go)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please follow the Conventional Commits specification for commit messages.


License

MIT


Acknowledgments


Made with ❤️ by jindun619