Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 107 additions & 129 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
# react-native-google-maps-plus

[![npm version](https://img.shields.io/npm/v/react-native-google-maps-plus.svg?logo=npm&color=cb0000)](https://www.npmjs.com/package/react-native-google-maps-plus)
[![Dev Release](https://img.shields.io/npm/v/react-native-google-maps-plus/dev.svg?label=dev%20release&color=orange&logo=githubactions)](https://www.npmjs.com/package/react-native-google-maps-plus)
[![Release](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml/badge.svg)](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml)
[![Issues](https://img.shields.io/github/issues/pinpong/react-native-google-maps-plus?logo=github)](https://github.com/pinpong/react-native-google-maps-plus/issues)
[![License](https://img.shields.io/github/license/pinpong/react-native-google-maps-plus?logo=open-source-initiative&logoColor=green)](./LICENSE)
[![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?logo=prettier&logoColor=white)](https://prettier.io/)
[![TypeScript](https://img.shields.io/badge/%3C/%3E-TypeScript-blue.svg?logo=typescript)](https://www.typescriptlang.org/)
[![Lint](https://img.shields.io/badge/lint-eslint-green.svg?logo=eslint&logoColor=white)](https://eslint.org/)
[![React Native](https://img.shields.io/badge/react--native-%3E%3D0.81.0-61dafb.svg?logo=react)](https://reactnative.dev/)
[![Platform: Android](https://img.shields.io/badge/platform-android-green.svg?logo=android&logoColor=white)](https://developer.android.com/)
[![Platform: iOS](https://img.shields.io/badge/platform-iOS-lightgrey.svg?logo=apple&logoColor=black)](https://developer.apple.com/ios/)
[![Dev Release](https://img.shields.io/npm/v/react-native-google-maps-plus/dev.svg?label=dev%20release&color=orange)](https://www.npmjs.com/package/react-native-google-maps-plus)
[![Build](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml/badge.svg)](https://github.com/pinpong/react-native-google-maps-plus/actions/workflows/release.yml)
![React Native](https://img.shields.io/badge/react--native-%3E%3D0.81.0-61dafb.svg?logo=react)
![Platform: Android](https://img.shields.io/badge/android-supported-brightgreen.svg?logo=android&logoColor=white)
![Platform: iOS](https://img.shields.io/badge/ios-supported-lightgrey.svg?logo=apple&logoColor=black)

React Native wrapper for Android & iOS Google Maps SDK.

Expand All @@ -22,6 +17,58 @@ React Native wrapper for Android & iOS Google Maps SDK.
yarn add react-native-google-maps-plus react-native-nitro-modules
```

**iOS**

Add this to your Podfile only for bare React Native apps.
(Not required for Expo, handled by the config plugin.)

```ruby
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
)
# Force iOS 16+ to avoid deployment target warnings
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end

# --- SVGKit Patch ---
require 'fileutils'
svgkit_path = File.join(installer.sandbox.pod_dir('SVGKit'), 'Source')

# node fix
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "Node.h"', '#import "SVGKit/Node.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched Node import in: #{file}"
end

# import CSSValue.h
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "CSSValue.h"', '#import "SVGKit/CSSValue.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched CSSValue import in: #{file}"
end

# import SVGLength.h
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "SVGLength.h"', '#import "SVGKit/SVGLength.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched SVGLength import in: #{file}"
end
end
```

### Expo Projects

Add your keys to the `app.json`.
Expand All @@ -43,148 +90,79 @@ The config plugin automatically injects them into your native Android and iOS bu
}
```

# Dependencies

This package builds on native libraries for SVG rendering and Google Maps integration:

- **iOS**: [SVGKit](https://github.com/SVGKit/SVGKit)
- **Android**: [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)
- **iOS Maps SDK**: [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk)
- **Android Maps SDK**: [Google Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk)
- **Maps Utility Libraries**: [Google Maps Utils for iOS](https://developers.google.com/maps/documentation/ios-sdk/utility) and [Google Maps Utils for Android](https://developers.google.com/maps/documentation/android-sdk/utility)

These are automatically linked when you install the package, but you may need to clean/rebuild your native projects after first install.

## Setup API Key

You will need a valid **Google Maps API Key** from the [Google Cloud Console](https://console.cloud.google.com/).

### Android

It's recommend to use [Secrets Gradle Plugin](https://developers.google.com/maps/documentation/android-sdk/secrets-gradle-plugin) to securely manage your Google Maps API Key.
**Note:** These instructions apply to **bare React Native apps only**.
Expo projects should use the config plugin instead (see Expo section above).

---
See the official [Google Maps Android SDK configuration guide](https://developers.google.com/maps/documentation/android-sdk/config#step_3_add_your_api_key_to_the_project) for more details.

### iOS

See the official [Google Maps iOS SDK configuration guide](https://developers.google.com/maps/documentation/ios-sdk/config#get-key) for more details.
**Note:** These instructions apply to **bare React Native apps only**.
Expo projects should use the config plugin instead (see Expo section above).

1. Create a `Secrets.xcconfig` file inside the **ios/** folder:
See the official [Google Maps iOS SDK configuration guide](https://developers.google.com/maps/documentation/ios-sdk/config#get-key) for more details.

```properties
MAPS_API_KEY=YOUR_IOS_MAPS_API_KEY
```
## Dependencies & Native Documentation

Include it in your project configuration file:
This package is React Native wrapper around the official Google Maps SDKs.
For full API behavior, configuration options, and feature reference, please consult the native documentation:

```xcconfig
#include? "Secrets.xcconfig"
```
- **iOS Google Maps SDK**
https://developers.google.com/maps/documentation/ios-sdk

2. Reference the API key in your **Info.plist**:
- **Android Google Maps SDK**
https://developers.google.com/maps/documentation/android-sdk

```xml
<key>MAPS_API_KEY</key>
<string>$(MAPS_API_KEY)</string>
```
- **Maps Utility Libraries (iOS & Android)**
https://developers.google.com/maps/documentation/ios-sdk/utility
https://developers.google.com/maps/documentation/android-sdk/utility

3. Provide the key programmatically in **AppDelegate.swift**:
- **SVG Rendering** (used for custom marker icons)
- iOS: https://github.com/SVGKit/SVGKit
- Android: https://bigbadaboom.github.io/androidsvg/

```swift
import GoogleMaps
These libraries are automatically linked during installation.
If you encounter build issues, try cleaning and rebuilding your native project.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "MAPS_API_KEY") as? String {
GMSServices.provideAPIKey(apiKey)
}
return true
}
}
```

---
> **Note:** This package follows the native SDKs closely. Props and behavior match the underlying Google Maps APIs whenever possible.

## Usage

Checkout the example app in the [example](./example) folder.

# Troubleshooting

## Android

- **API key not found**
Make sure `secrets.properties` exists under `android/` and contains your `MAPS_API_KEY`.
Run `./gradlew clean` and rebuild.

## iOS

- **`GMSServices must be configured before use`**
Ensure your key is in `Info.plist` and/or provided via `GMSServices.provideAPIKey(...)` in `AppDelegate.swift`.

- **Build fails with `Node.h`, `CSSValue.h`, or `SVGLength.h` import errors from SVGKit**
SVGKit includes headers (`Node.h`, `CSSValue.h`, `SVGLength.h`) that can conflict with
iOS system headers and React Native Reanimated’s internal types.
You can patch them automatically in your **Podfile** inside the `post_install`

```ruby
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
)
# Force iOS 16+ to avoid deployment target warnings
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end

# --- SVGKit Patch ---
require 'fileutils'
svgkit_path = File.join(installer.sandbox.pod_dir('SVGKit'), 'Source')

# node fix
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "Node.h"', '#import "SVGKit/Node.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched Node import in: #{file}"
end

# import CSSValue.h
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "CSSValue.h"', '#import "SVGKit/CSSValue.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched CSSValue import in: #{file}"
end

# import SVGLength.h
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
FileUtils.chmod("u+w", file)
text = File.read(file)
new_contents = text.gsub('#import "SVGLength.h"', '#import "SVGKit/SVGLength.h"')
File.open(file, 'w') { |f| f.write(new_contents) }
# puts "Patched SVGLength import in: #{file}"
end
end
```

After applying this, run:

```sh
cd ios && pod install --repo-update
```
Basic map:

```tsx
import React from 'react';
import { GoogleMapsView } from 'react-native-google-maps-plus';

export default function App() {
return (
<GoogleMapsView
style={{ flex: 1 }}
initialProps={{
camera: {
center: { latitude: 37.7749, longitude: -122.4194 },
zoom: 12,
},
}}
markers={[
{
id: '1',
zIndex: 1,
coordinate: { latitude: 37.7749, longitude: -122.4194 },
},
]}
/>
);
}
```

- **Maps not rendering**
- Check that your API key has **Maps SDK for Android/iOS** enabled in Google Cloud Console.
- Make sure the key is not restricted to wrong bundle IDs or SHA1 fingerprints.
Check out the example app in the [example](./example) folder.

## Contributing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ fun RNUserInterfaceStyle?.toMapColorScheme(): Int? =
when (this) {
RNUserInterfaceStyle.LIGHT -> MapColorScheme.LIGHT
RNUserInterfaceStyle.DARK -> MapColorScheme.DARK
RNUserInterfaceStyle.DEFAULT -> MapColorScheme.FOLLOW_SYSTEM
RNUserInterfaceStyle.SYSTEM -> MapColorScheme.FOLLOW_SYSTEM
null -> null
}
12 changes: 9 additions & 3 deletions example/src/components/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
import { useAppTheme } from '../hooks/useAppTheme';
import { useNavigation } from '@react-navigation/native';
import type { RootNavigationProp } from '../types/navigation';
import {
type EdgeInsets,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import type { AppTheme } from '../theme';

export type ButtonItem = { title: string; onPress: () => void };

Expand All @@ -29,7 +34,8 @@ export default function ControlPanel({ mapRef, buttons }: Props) {
const theme = useAppTheme();
const navigation = useNavigation<RootNavigationProp>();
const progress = useSharedValue(0);
const styles = useMemo(() => getThemedStyles(theme), [theme]);
const layout = useSafeAreaInsets();
const styles = useMemo(() => getThemedStyles(theme, layout), [layout, theme]);

const toggle = () => {
progress.value = withTiming(progress.value === 1 ? 0 : 1, {
Expand Down Expand Up @@ -124,7 +130,7 @@ export default function ControlPanel({ mapRef, buttons }: Props) {
);
}

const getThemedStyles = (theme: any) =>
const getThemedStyles = (theme: AppTheme, layout: EdgeInsets) =>
StyleSheet.create({
scrollView: {
position: 'absolute',
Expand All @@ -136,7 +142,7 @@ const getThemedStyles = (theme: any) =>
backgroundColor: theme.bgPrimary,
},
scrollContent: {
paddingBottom: 40,
paddingBottom: layout.bottom + 8,
},
header: {
borderRadius: 10,
Expand Down
3 changes: 2 additions & 1 deletion example/src/components/HeaderButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useMemo } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import { useAppTheme } from '../hooks/useAppTheme';
import type { AppTheme } from '../theme';

type Props = {
title: string;
Expand All @@ -18,7 +19,7 @@ export default function HeaderButton({ title, onPress }: Props) {
);
}

const getThemedStyles = (theme: any) =>
const getThemedStyles = (theme: AppTheme) =>
StyleSheet.create({
headerButton: {
paddingHorizontal: 12,
Expand Down
14 changes: 8 additions & 6 deletions example/src/components/MapWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
} from 'react-native-google-maps-plus';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { callback } from 'react-native-nitro-modules';
import { useTheme } from '@react-navigation/native';
import type { AppTheme } from '../theme';
import { useAppTheme } from '../hooks/useAppTheme';

type Props = ViewProps &
RNGoogleMapsPlusViewProps & {
Expand All @@ -47,7 +48,7 @@ function wrapCallback<T extends (...args: any[]) => void>(

export default function MapWrapper(props: Props) {
const { children, ...rest } = props;
const theme = useTheme();
const theme = useAppTheme();
const styles = useMemo(() => getThemedStyles(theme), [theme]);
const layout = useSafeAreaInsets();

Expand Down Expand Up @@ -119,7 +120,8 @@ export default function MapWrapper(props: Props) {
indoorEnabled={props.indoorEnabled ?? false}
style={[styles.map, props.style]}
userInterfaceStyle={
props.userInterfaceStyle ?? (theme.dark ? 'dark' : 'light')
props.userInterfaceStyle ??
(theme.theme === 'dark' ? 'dark' : 'light')
}
mapType={props.mapType ?? 'normal'}
mapZoomConfig={props.mapZoomConfig ?? mapZoomConfig}
Expand Down Expand Up @@ -231,18 +233,18 @@ export default function MapWrapper(props: Props) {
{children}
{!mapLoaded && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color={theme.colors.primary} />
<ActivityIndicator size="large" color={theme.bgAccent} />
</View>
)}
</View>
);
}

const getThemedStyles = (theme: any) =>
const getThemedStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.background,
backgroundColor: theme.bgPrimary,
},
map: {
position: 'absolute',
Expand Down
Loading
Loading