Skip to content

Commit d3bbcfe

Browse files
authored
chore: merge dev into main
2 parents 02eb547 + d66534f commit d3bbcfe

13 files changed

Lines changed: 700 additions & 186 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,4 @@ android/.kotlin/
9494
android/.kotlinc/
9595

9696
tsconfig.tsbuildinfo
97+
expoConfig/build

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ React-native wrapper for android & IOS google maps sdk
2222
yarn add react-native-google-maps-plus react-native-nitro-modules
2323
```
2424

25+
### Expo Projects
26+
27+
Add your keys to the `app.json`.
28+
The config plugin automatically injects them into your native Android and iOS builds during `expo prebuild`.
29+
30+
```json
31+
{
32+
"expo": {
33+
"plugins": [
34+
[
35+
"react-native-google-maps-plus",
36+
{
37+
"googleMapsAndroidApiKey": "YOUR_ANDROID_MAPS_API_KEY",
38+
"googleMapsIosApiKey": "YOUR_IOS_MAPS_API_KEY"
39+
}
40+
]
41+
]
42+
}
43+
}
44+
```
45+
2546
# Dependencies
2647

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

app.plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./expoConfig/build');

eslint.config.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ export default defineConfig([
2424
},
2525
},
2626
{
27-
ignores: ['node_modules/', 'lib/', '.yarn', 'eslint.config.mjs'],
27+
ignores: [
28+
'node_modules/',
29+
'lib/',
30+
'expoConfig/build',
31+
'.yarn',
32+
'eslint.config.mjs',
33+
],
2834
},
2935
]);

example/ios/Podfile.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ PODS:
1616
- hermes-engine (0.82.0):
1717
- hermes-engine/Pre-built (= 0.82.0)
1818
- hermes-engine/Pre-built (0.82.0)
19-
- NitroModules (0.30.0):
19+
- NitroModules (0.30.1):
2020
- boost
2121
- DoubleConversion
2222
- fast_float
@@ -2601,7 +2601,7 @@ PODS:
26012601
- RNWorklets
26022602
- SocketRocket
26032603
- Yoga
2604-
- RNScreens (4.16.0):
2604+
- RNScreens (4.17.1):
26052605
- boost
26062606
- DoubleConversion
26072607
- fast_float
@@ -2628,10 +2628,10 @@ PODS:
26282628
- ReactCodegen
26292629
- ReactCommon/turbomodule/bridging
26302630
- ReactCommon/turbomodule/core
2631-
- RNScreens/common (= 4.16.0)
2631+
- RNScreens/common (= 4.17.1)
26322632
- SocketRocket
26332633
- Yoga
2634-
- RNScreens/common (4.16.0):
2634+
- RNScreens/common (4.17.1):
26352635
- boost
26362636
- DoubleConversion
26372637
- fast_float
@@ -3023,7 +3023,7 @@ SPEC CHECKSUMS:
30233023
Google-Maps-iOS-Utils: bed22fa703c919259b3901449434d60d994fae20
30243024
GoogleMaps: a40d3b1f511f0fa2036e7b08c920c33279b1d5fd
30253025
hermes-engine: 8642d8f14a548ab718ec112e9bebdfdd154138b5
3026-
NitroModules: 9d7097ba832aa88b678bf65b8a04e5ea565334d8
3026+
NitroModules: 49f9875e3f1d06a125d0522b80ca502e8788eb8e
30273027
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
30283028
RCTDeprecation: 22bf66112da540a7d40e536366ddd8557934fca1
30293029
RCTRequired: a0ed4dc41b35f79fbb6d8ba320e06882a8c792cf
@@ -3094,7 +3094,7 @@ SPEC CHECKSUMS:
30943094
RNGestureHandler: e1cc4de7646eb557ad62d1271d8eac73c304a896
30953095
RNGoogleMapsPlus: 43e90cbedb2f3deec67a3a0a14fb5065f608ea3c
30963096
RNReanimated: 8f0185df21f0dea34ee8c9611ba88c17a290ed9a
3097-
RNScreens: 2e9c41cd099b1ca50136af8d57c3594214d0086a
3097+
RNScreens: ccfcc2f7d9c0d458b7fc41b3f4f0bea054602b3a
30983098
RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf
30993099
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
31003100
SVGKit: 1ad7513f8c74d9652f94ed64ddecda1a23864dea

example/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
},
1313
"dependencies": {
1414
"@react-navigation/native": "7.1.18",
15-
"@react-navigation/native-stack": "7.3.27",
16-
"@react-navigation/stack": "7.4.9",
15+
"@react-navigation/native-stack": "7.3.28",
16+
"@react-navigation/stack": "7.4.10",
1717
"react": "19.1.1",
1818
"react-hook-form": "7.65.0",
1919
"react-native": "0.82.0",
2020
"react-native-clusterer": "4.0.0",
2121
"react-native-gesture-handler": "2.28.0",
2222
"react-native-google-maps-plus": "workspace:*",
23-
"react-native-nitro-modules": "0.30.0",
23+
"react-native-nitro-modules": "0.30.1",
2424
"react-native-reanimated": "4.1.3",
2525
"react-native-safe-area-context": "5.6.1",
26-
"react-native-screens": "4.16.0",
26+
"react-native-screens": "4.17.1",
2727
"react-native-worklets": "0.6.1",
28-
"superstruct": "^2.0.2"
28+
"superstruct": "2.0.2"
2929
},
3030
"devDependencies": {
3131
"@babel/core": "7.28.4",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { withAndroidManifest } from '@expo/config-plugins';
2+
import type { ConfigPlugin } from '@expo/config-plugins';
3+
import {
4+
addMetaDataItemToMainApplication,
5+
getMainApplicationOrThrow,
6+
removeMetaDataItemFromMainApplication,
7+
} from '@expo/config-plugins/build/android/Manifest';
8+
import type { RNGoogleMapsPlusExpoPluginProps } from '../types';
9+
10+
const withMapsAndroid: ConfigPlugin<RNGoogleMapsPlusExpoPluginProps> = (
11+
config,
12+
props
13+
) => {
14+
return withAndroidManifest(config, (conf) => {
15+
const manifest = conf.modResults;
16+
const mainApplication = getMainApplicationOrThrow(manifest);
17+
18+
const apiKey =
19+
props.googleMapsAndroidApiKey ??
20+
process.env.GOOGLE_MAPS_API_KEY_ANDROID ??
21+
null;
22+
23+
if (apiKey) {
24+
addMetaDataItemToMainApplication(
25+
mainApplication,
26+
'com.google.android.geo.API_KEY',
27+
apiKey
28+
);
29+
} else {
30+
removeMetaDataItemFromMainApplication(
31+
mainApplication,
32+
'com.google.android.geo.API_KEY'
33+
);
34+
console.warn(
35+
'[react-native-google-maps-plus] No Android API key provided. Removed existing entry.'
36+
);
37+
}
38+
39+
return conf;
40+
});
41+
};
42+
43+
export default withMapsAndroid;

expoConfig/src/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ConfigPlugin } from '@expo/config-plugins';
2+
import type { RNGoogleMapsPlusExpoPluginProps } from './types';
3+
import fs from 'fs';
4+
import path from 'path';
5+
6+
const pkg = JSON.parse(
7+
fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8')
8+
);
9+
10+
import { createRunOncePlugin } from '@expo/config-plugins';
11+
import withIosGoogleMapsPlus from './ios/withIosGoogleMapsPlus';
12+
import withAndroidGoogleMapsPlus from './android/withAndroidGoogleMapsPlus';
13+
14+
const withGoogleMapsPlus: ConfigPlugin<RNGoogleMapsPlusExpoPluginProps> = (
15+
config,
16+
props
17+
) => {
18+
config = withAndroidGoogleMapsPlus(config, props);
19+
config = withIosGoogleMapsPlus(config, props);
20+
return config;
21+
};
22+
23+
module.exports = createRunOncePlugin(withGoogleMapsPlus, pkg.name, pkg.version);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {
2+
withInfoPlist,
3+
withPodfile,
4+
withAppDelegate,
5+
type ConfigPlugin,
6+
} from '@expo/config-plugins';
7+
import { mergeContents } from '@expo/config-plugins/build/utils/generateCode';
8+
import type { RNGoogleMapsPlusExpoPluginProps } from '../types';
9+
10+
const withIosGoogleMapsPlus: ConfigPlugin<RNGoogleMapsPlusExpoPluginProps> = (
11+
config,
12+
props
13+
) => {
14+
config = withInfoPlist(config, (conf) => {
15+
const apiKey =
16+
props.googleMapsIosApiKey ?? process.env.GOOGLE_MAPS_API_KEY_IOS ?? null;
17+
18+
if (!apiKey) {
19+
console.warn(
20+
'[react-native-google-maps-plus] No iOS API key provided. Google Maps may fail to initialize.'
21+
);
22+
}
23+
if (apiKey) {
24+
conf.modResults.MAPS_API_KEY = apiKey;
25+
}
26+
return conf;
27+
});
28+
29+
config = withPodfile(config, (conf) => {
30+
let src = conf.modResults.contents;
31+
if (!src.includes('use_modular_headers!')) {
32+
src = mergeContents({
33+
tag: 'react-native-google-maps-modular-headers',
34+
src,
35+
newSrc: 'use_modular_headers!',
36+
anchor: /use_frameworks!|platform\s+:ios.*/,
37+
offset: 1,
38+
comment: '#',
39+
}).contents;
40+
}
41+
42+
const patchSnippet = `
43+
# Force iOS 16+ to avoid deployment target warnings
44+
installer.pods_project.targets.each do |target|
45+
target.build_configurations.each do |config|
46+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
47+
end
48+
end
49+
50+
# --- SVGKit Patch ---
51+
require 'fileutils'
52+
svgkit_path = File.join(installer.sandbox.pod_dir('SVGKit'), 'Source')
53+
54+
# --- Patch Node.h imports ---
55+
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
56+
FileUtils.chmod("u+w", file)
57+
text = File.read(file)
58+
new_contents = text.gsub('#import "Node.h"', '#import "SVGKit/Node.h"')
59+
File.open(file, 'w') { |f| f.write(new_contents) }
60+
end
61+
62+
# --- Patch CSSValue.h imports ---
63+
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
64+
FileUtils.chmod("u+w", file)
65+
text = File.read(file)
66+
new_contents = text.gsub('#import "CSSValue.h"', '#import "SVGKit/CSSValue.h"')
67+
File.open(file, 'w') { |f| f.write(new_contents) }
68+
end
69+
70+
# --- Patch SVGLength.h imports ---
71+
Dir.glob(File.join(svgkit_path, '**', '*.{h,m}')).each do |file|
72+
FileUtils.chmod("u+w", file)
73+
text = File.read(file)
74+
new_contents = text.gsub('#import "SVGLength.h"', '#import "SVGKit/SVGLength.h"')
75+
File.open(file, 'w') { |f| f.write(new_contents) }
76+
end
77+
`;
78+
79+
if (src.includes('post_install do |installer|')) {
80+
src = src.replace(
81+
/post_install do \|installer\|([\s\S]*?)end/,
82+
(match, inner) => {
83+
if (inner.includes('SVGKit Patch')) return match; // idempotent
84+
return `post_install do |installer|${inner}\n${patchSnippet}\nend`;
85+
}
86+
);
87+
} else {
88+
src += `\npost_install do |installer|\n${patchSnippet}\nend\n`;
89+
}
90+
91+
conf.modResults.contents = src;
92+
return conf;
93+
});
94+
95+
config = withAppDelegate(config, (conf) => {
96+
const { language } = conf.modResults;
97+
if (language !== 'swift') {
98+
console.warn(
99+
'[react-native-google-maps-plus] AppDelegate is not Swift; skipping GMSServices injection.'
100+
);
101+
return conf;
102+
}
103+
104+
let src = conf.modResults.contents;
105+
106+
if (!src.includes('import GoogleMaps')) {
107+
src = mergeContents({
108+
tag: 'react-native-google-maps-import',
109+
src,
110+
newSrc: 'import GoogleMaps',
111+
anchor: /^import React/m,
112+
offset: 1,
113+
comment: '//',
114+
}).contents;
115+
}
116+
117+
const initSnippet = `
118+
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "MAPS_API_KEY") as? String {
119+
GMSServices.provideAPIKey(apiKey)
120+
}`;
121+
122+
try {
123+
src = mergeContents({
124+
tag: 'react-native-google-maps-init',
125+
src,
126+
newSrc: initSnippet,
127+
anchor:
128+
/return\s+super\.application\s*\(\s*application\s*,\s*didFinishLaunchingWithOptions:\s*launchOptions\s*\)/,
129+
offset: -1,
130+
comment: '//',
131+
}).contents;
132+
} catch (error: any) {
133+
if (error.code === 'ERR_NO_MATCH') {
134+
src = mergeContents({
135+
tag: 'react-native-google-maps-init',
136+
src,
137+
newSrc: initSnippet,
138+
anchor: /didFinishLaunchingWithOptions[^{]*\{/,
139+
offset: 1,
140+
comment: '//',
141+
}).contents;
142+
} else {
143+
throw error;
144+
}
145+
}
146+
147+
conf.modResults.contents = src;
148+
return conf;
149+
});
150+
151+
return config;
152+
};
153+
154+
export default withIosGoogleMapsPlus;

expoConfig/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type RNGoogleMapsPlusExpoPluginProps = {
2+
googleMapsAndroidApiKey: string;
3+
googleMapsIosApiKey: string;
4+
};

0 commit comments

Comments
 (0)