Skip to content

Commit 54c25c6

Browse files
authored
fix!: cross platform mapStyle prop handling (#565)
Updates mapStyle prop handling on Android to match the iOS functionality. Both platforms now take json data styling string as property value. BREAKING CHANGE: mapStyle prop on Android now expects the JSON styling string directly, matching iOS behavior. Previously Android incorrectly expected a URL to a JSON style file.
1 parent 1dd2087 commit 54c25c6

File tree

14 files changed

+248
-61
lines changed

14 files changed

+248
-61
lines changed

android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
public interface IMapViewFragment extends IMapViewProperties {
2020
MapViewController getMapController();
2121

22-
void setMapStyle(String url);
22+
void setMapStyle(String mapStyle);
2323

2424
GoogleMap getGoogleMap();
2525

android/src/main/java/com/google/android/react/navsdk/MapViewController.java

Lines changed: 14 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,10 @@
3636
import com.google.android.gms.maps.model.PolygonOptions;
3737
import com.google.android.gms.maps.model.Polyline;
3838
import com.google.android.gms.maps.model.PolylineOptions;
39-
import java.io.BufferedReader;
40-
import java.io.IOException;
41-
import java.io.InputStream;
42-
import java.io.InputStreamReader;
43-
import java.net.HttpURLConnection;
44-
import java.net.URL;
4539
import java.util.ArrayList;
4640
import java.util.HashMap;
4741
import java.util.List;
4842
import java.util.Map;
49-
import java.util.concurrent.Executors;
5043

5144
public class MapViewController implements INavigationViewControllerProperties {
5245
private GoogleMap mGoogleMap;
@@ -67,8 +60,6 @@ public class MapViewController implements INavigationViewControllerProperties {
6760
private final Map<String, String> groundOverlayNativeIdToEffectiveId = new HashMap<>();
6861
private final Map<String, String> circleNativeIdToEffectiveId = new HashMap<>();
6962

70-
private String style = "";
71-
7263
// Zoom level preferences (-1 means use map's current value)
7364
private Float minZoomLevelPreference = null;
7465
private Float maxZoomLevelPreference = null;
@@ -770,25 +761,20 @@ public Map<String, GroundOverlay> getGroundOverlayMap() {
770761
return groundOverlayMap;
771762
}
772763

773-
public void setMapStyle(String url) {
774-
Executors.newSingleThreadExecutor()
775-
.execute(
776-
() -> {
777-
try {
778-
style = fetchJsonFromUrl(url);
779-
} catch (IOException e) {
780-
throw new RuntimeException(e);
781-
}
782-
783-
Activity activity = activitySupplier.get();
784-
if (activity != null) {
785-
activity.runOnUiThread(
786-
() -> {
787-
MapStyleOptions options = new MapStyleOptions(style);
788-
mGoogleMap.setMapStyle(options);
789-
});
790-
}
791-
});
764+
public void setMapStyle(String styleJson) {
765+
Activity activity = activitySupplier.get();
766+
if (activity != null) {
767+
activity.runOnUiThread(
768+
() -> {
769+
if (styleJson == null || styleJson.isEmpty()) {
770+
// Reset to default map style
771+
mGoogleMap.setMapStyle(null);
772+
} else {
773+
MapStyleOptions options = new MapStyleOptions(styleJson);
774+
mGoogleMap.setMapStyle(options);
775+
}
776+
});
777+
}
792778
}
793779

794780
/** Moves the position of the camera to the specified location. */
@@ -1037,29 +1023,6 @@ public void setPadding(int top, int left, int bottom, int right) {
10371023
}
10381024
}
10391025

1040-
private String fetchJsonFromUrl(String urlString) throws IOException {
1041-
URL url = new URL(urlString);
1042-
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1043-
connection.setRequestMethod("GET");
1044-
1045-
int responseCode = connection.getResponseCode();
1046-
if (responseCode == HttpURLConnection.HTTP_OK) {
1047-
InputStream inputStream = connection.getInputStream();
1048-
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
1049-
StringBuilder stringBuilder = new StringBuilder();
1050-
String line;
1051-
while ((line = reader.readLine()) != null) {
1052-
stringBuilder.append(line);
1053-
}
1054-
reader.close();
1055-
inputStream.close();
1056-
return stringBuilder.toString();
1057-
} else {
1058-
// Handle error response
1059-
throw new IOException("Error response: " + responseCode);
1060-
}
1061-
}
1062-
10631026
private LatLng createLatLng(Map<String, Object> map) {
10641027
Double lat = null;
10651028
Double lng = null;

android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ public MapViewController getMapController() {
141141
return mMapViewController;
142142
}
143143

144-
public void setMapStyle(String url) {
145-
mMapViewController.setMapStyle(url);
144+
public void setMapStyle(String mapStyle) {
145+
mMapViewController.setMapStyle(mapStyle);
146146
}
147147

148148
public GoogleMap getGoogleMap() {

android/src/main/java/com/google/android/react/navsdk/NavAutoModule.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,12 @@ public void setMapType(double mapType) {
138138

139139
@Override
140140
public void setMapStyle(String mapStyle) {
141-
String url = mapStyle;
142141
UiThreadUtil.runOnUiThread(
143142
() -> {
144143
if (mMapViewController == null) {
145144
return;
146145
}
147-
mMapViewController.setMapStyle(url);
146+
mMapViewController.setMapStyle(mapStyle);
148147
});
149148
}
150149

android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ public MapViewController getMapController() {
122122
return mMapViewController;
123123
}
124124

125-
public void setMapStyle(String url) {
126-
mMapViewController.setMapStyle(url);
125+
public void setMapStyle(String mapStyle) {
126+
mMapViewController.setMapStyle(mapStyle);
127127
}
128128

129129
public void applyStylingOptions() {

example/.detoxrc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ module.exports = {
6060
simulator: {
6161
type: 'ios.simulator',
6262
device: {
63-
type: 'iPhone 16 Pro',
64-
os: 'iOS 18.6',
63+
type: 'iPhone 17 Pro',
64+
os: 'iOS 26.4',
6565
},
6666
},
6767
attached: {

example/e2e/map.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,11 @@ describe('Map view tests', () => {
8282
await expectNoErrors();
8383
await expectSuccess();
8484
});
85+
86+
it('MT09 - test setting map style via JSON', async () => {
87+
await selectTestByName('testMapStyle');
88+
await waitForTestToFinish();
89+
await expectNoErrors();
90+
await expectSuccess();
91+
});
8592
});

example/ios/Podfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,17 @@ post_install do |installer|
5959
:mac_catalyst_enabled => false,
6060
# :ccache_enabled => true
6161
)
62+
63+
# Fix fmt compilation issue with Xcode 26.4+ (stricter consteval enforcement)
64+
# fmt/base.h unconditionally redefines FMT_USE_CONSTEVAL based on __cplusplus,
65+
# so preprocessor defines are overwritten. Compiling fmt in C++17 mode ensures
66+
# FMT_CPLUSPLUS (201703L) < 201709L → FMT_USE_CONSTEVAL = 0.
67+
# Fixed in React Native 0.85+, can be removed after upgrading.
68+
installer.pods_project.targets.each do |target|
69+
if target.name == 'fmt'
70+
target.build_configurations.each do |config|
71+
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
72+
end
73+
end
74+
end
6275
end

example/src/controls/mapsControls.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export interface MapControlsProps {
6969
readonly onZoomControlsEnabledChange?: (enabled: boolean) => void;
7070
readonly zoomGesturesEnabled?: boolean;
7171
readonly onZoomGesturesEnabledChange?: (enabled: boolean) => void;
72+
// Map style
73+
readonly mapStyleEnabled?: boolean;
74+
readonly onMapStyleEnabledChange?: (enabled: boolean) => void;
7275
}
7376

7477
export const defaultZoom: number = 15;
@@ -103,6 +106,8 @@ const MapsControls: React.FC<MapControlsProps> = ({
103106
onZoomControlsEnabledChange,
104107
zoomGesturesEnabled = true,
105108
onZoomGesturesEnabledChange,
109+
mapStyleEnabled = false,
110+
onMapStyleEnabledChange,
106111
}) => {
107112
const mapTypeOptions = ['None', 'Normal', 'Satellite', 'Terrain', 'Hybrid'];
108113
const colorSchemeOptions = ['Follow System', 'Light', 'Dark'];
@@ -517,6 +522,13 @@ const MapsControls: React.FC<MapControlsProps> = ({
517522
onPress={toggleCustomPadding}
518523
/>
519524
</View>
525+
<View style={ControlStyles.rowContainer}>
526+
<Text style={ControlStyles.rowLabel}>JSON Styling (Night mode)</Text>
527+
<ExampleAppButton
528+
title={mapStyleEnabled ? 'Disable' : 'Enable'}
529+
onPress={() => onMapStyleEnabledChange?.(!mapStyleEnabled)}
530+
/>
531+
</View>
520532
</Accordion>
521533

522534
{/* Location & UI */}

example/src/screens/IntegrationTestsScreen.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
testNavigationStateGuards,
5555
testStartGuidanceWithoutDestinations,
5656
testRouteTokenOptionsValidation,
57+
testMapStyle,
5758
NO_ERRORS_DETECTED_LABEL,
5859
} from './integration_tests/integration_test';
5960

@@ -124,6 +125,7 @@ const IntegrationTestsScreen = () => {
124125
const [mapToolbarEnabled, setMapToolbarEnabled] = useState<
125126
boolean | undefined
126127
>(undefined);
128+
const [mapStyle, setMapStyle] = useState<string | undefined>(undefined);
127129

128130
const onMapReady = useCallback(async () => {
129131
try {
@@ -231,6 +233,7 @@ const IntegrationTestsScreen = () => {
231233
setZoomGesturesEnabled,
232234
setZoomControlsEnabled,
233235
setMapToolbarEnabled,
236+
setMapStyle,
234237
};
235238
};
236239

@@ -297,6 +300,9 @@ const IntegrationTestsScreen = () => {
297300
case 'testRouteTokenOptionsValidation':
298301
await testRouteTokenOptionsValidation(getTestTools());
299302
break;
303+
case 'testMapStyle':
304+
await testMapStyle(getTestTools());
305+
break;
300306
default:
301307
resetTestState();
302308
break;
@@ -338,6 +344,7 @@ const IntegrationTestsScreen = () => {
338344
zoomGesturesEnabled={zoomGesturesEnabled}
339345
zoomControlsEnabled={zoomControlsEnabled}
340346
mapToolbarEnabled={mapToolbarEnabled}
347+
mapStyle={mapStyle}
341348
/>
342349
</View>
343350
<View style={{ flex: 4 }}>
@@ -502,6 +509,13 @@ const IntegrationTestsScreen = () => {
502509
}}
503510
testID="testRouteTokenOptionsValidation"
504511
/>
512+
<ExampleAppButton
513+
title="testMapStyle"
514+
onPress={() => {
515+
runTest('testMapStyle');
516+
}}
517+
testID="testMapStyle"
518+
/>
505519
</OverlayModal>
506520
</View>
507521
);

0 commit comments

Comments
 (0)