From b574fc3a3dba4fcf8994a2a68fbd33b98c781f07 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 20 Nov 2025 09:46:26 +0200 Subject: [PATCH 1/3] feat: support for mapId --- README.md | 2 + android/build.gradle | 2 +- .../react/navsdk/IMapViewFragment.java | 5 - .../react/navsdk/INavViewFragment.java | 6 + .../android/react/navsdk/MapViewFragment.java | 55 +- .../android/react/navsdk/NavViewFragment.java | 79 +- .../android/react/navsdk/NavViewLayout.java | 55 - .../android/react/navsdk/NavViewManager.java | 220 ++- example/android/app/build.gradle | 2 +- example/src/App.tsx | 78 +- example/src/controls/mapsControls.tsx | 30 +- example/src/controls/navigationControls.tsx | 110 +- .../src/screens/IntegrationTestsScreen.tsx | 6 +- example/src/screens/MapIdScreen.tsx | 298 ++++ example/src/screens/MultipleMapsScreen.tsx | 40 +- example/src/screens/NavigationScreen.tsx | 28 +- example/src/styles.ts | 102 -- example/src/styles/components.ts | 302 ++++ example/src/styles/index.ts | 29 + example/src/styles/mapStyling.ts | 42 + example/src/styles/theme.ts | 125 ++ .../BaseCarSceneDelegate.m | 2 +- ios/react-native-navigation-sdk/NavView.h | 4 +- ios/react-native-navigation-sdk/NavView.m | 24 +- .../NavViewController.h | 6 +- .../NavViewController.m | 66 +- .../RCTNavViewManager.m | 27 +- src/maps/mapView/mapView.tsx | 5 +- src/maps/types.ts | 8 + .../navigationView/navigationView.tsx | 14 +- src/navigation/navigationView/types.ts | 38 +- src/shared/viewManager.ts | 8 +- yarn.lock | 1556 +++++++++-------- 33 files changed, 2079 insertions(+), 1295 deletions(-) delete mode 100644 android/src/main/java/com/google/android/react/navsdk/NavViewLayout.java create mode 100644 example/src/screens/MapIdScreen.tsx delete mode 100644 example/src/styles.ts create mode 100644 example/src/styles/components.ts create mode 100644 example/src/styles/index.ts create mode 100644 example/src/styles/mapStyling.ts create mode 100644 example/src/styles/theme.ts diff --git a/README.md b/README.md index b8021bfe..47958919 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ in an unbounded widget will cause the application to behave unexpectedly. // Permissions must have been granted by this point. diff --git a/android/build.gradle b/android/build.gradle index 7e27596b..ed48cf04 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -82,6 +82,6 @@ dependencies { implementation "androidx.car.app:app:1.4.0" implementation "androidx.car.app:app-projected:1.4.0" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "com.google.android.libraries.navigation:navigation:7.0.0" + implementation "com.google.android.libraries.navigation:navigation:7.1.0" api 'com.google.guava:guava:31.0.1-android' } diff --git a/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java index f5cde85f..77813599 100644 --- a/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java @@ -15,15 +15,10 @@ import android.view.View; import com.google.android.gms.maps.GoogleMap; -import com.google.android.libraries.navigation.StylingOptions; public interface IMapViewFragment { MapViewController getMapController(); - void setStylingOptions(StylingOptions stylingOptions); - - void applyStylingOptions(); - void setMapStyle(String url); GoogleMap getGoogleMap(); diff --git a/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java index c47790b2..7253ff4f 100644 --- a/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java @@ -13,6 +13,8 @@ */ package com.google.android.react.navsdk; +import com.google.android.libraries.navigation.StylingOptions; + public interface INavViewFragment extends IMapViewFragment { void setNavigationUiEnabled(boolean enableNavigationUi); @@ -35,4 +37,8 @@ public interface INavViewFragment extends IMapViewFragment { void setNightModeOption(int jsValue); void setReportIncidentButtonEnabled(boolean enabled); + + void setStylingOptions(StylingOptions stylingOptions); + + void applyStylingOptions(); } diff --git a/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java index 740555f2..5b007829 100644 --- a/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java @@ -25,7 +25,7 @@ import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; @@ -33,9 +33,6 @@ import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; -import com.google.android.libraries.navigation.StylingOptions; -import java.util.ArrayList; -import java.util.List; /** * A fragment that displays a view with a Google Map using MapFragment. This fragment's lifecycle is @@ -45,25 +42,23 @@ public class MapViewFragment extends SupportMapFragment implements IMapViewFragment, INavigationViewCallback { private static final String TAG = "MapViewFragment"; + private int viewTag; // React native view tag. + private ReactApplicationContext reactContext; private GoogleMap mGoogleMap; private MapViewController mMapViewController; - private StylingOptions mStylingOptions; - private List markerList = new ArrayList<>(); - private List polylineList = new ArrayList<>(); - private List polygonList = new ArrayList<>(); - private List groundOverlayList = new ArrayList<>(); - private List circleList = new ArrayList<>(); - private int viewTag; // React native view tag. - private ReactApplicationContext reactContext; + public static MapViewFragment newInstance( + ReactApplicationContext reactContext, int viewTag, @NonNull GoogleMapOptions mapOptions) { + MapViewFragment fragment = new MapViewFragment(); + Bundle args = new Bundle(); + args.putParcelable("MapOptions", mapOptions); - public MapViewFragment(ReactApplicationContext reactContext, int viewTag) { - this.reactContext = reactContext; - this.viewTag = viewTag; - } - ; + fragment.setArguments(args); + fragment.reactContext = reactContext; + fragment.viewTag = viewTag; - private String style = ""; + return fragment; + } @SuppressLint("MissingPermission") @Override @@ -71,17 +66,21 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); getMapAsync( - new OnMapReadyCallback() { - public void onMapReady(GoogleMap googleMap) { - mGoogleMap = googleMap; + googleMap -> { + mGoogleMap = googleMap; + + mMapViewController = new MapViewController(); + mMapViewController.initialize(googleMap, this::requireActivity); - mMapViewController = new MapViewController(); - mMapViewController.initialize(googleMap, () -> requireActivity()); + // Setup map listeners with the provided callback + mMapViewController.setupMapListeners(MapViewFragment.this); - // Setup map listeners with the provided callback - mMapViewController.setupMapListeners(MapViewFragment.this); + emitEvent("onMapReady", null); - emitEvent("onMapReady", null); + // Request layout to ensure fragment is properly sized + View fragmentView = getView(); + if (fragmentView != null) { + fragmentView.requestLayout(); } }); } @@ -130,10 +129,6 @@ public MapViewController getMapController() { return mMapViewController; } - public void applyStylingOptions() {} - - public void setStylingOptions(StylingOptions stylingOptions) {} - public void setMapStyle(String url) { mMapViewController.setMapStyle(url); } diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java index 579356c1..a225139d 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java @@ -25,7 +25,7 @@ import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; @@ -44,16 +44,54 @@ public class NavViewFragment extends SupportNavigationFragment implements INavViewFragment, INavigationViewCallback { private static final String TAG = "NavViewFragment"; + private int viewTag; // React native view tag. + private ReactApplicationContext reactContext; private MapViewController mMapViewController; private GoogleMap mGoogleMap; private StylingOptions mStylingOptions; - private int viewTag; // React native view tag. - private ReactApplicationContext reactContext; + public static NavViewFragment newInstance( + ReactApplicationContext reactContext, int viewTag, @NonNull GoogleMapOptions mapOptions) { + NavViewFragment fragment = new NavViewFragment(); + Bundle args = new Bundle(); + args.putParcelable("MapOptions", mapOptions); + + fragment.setArguments(args); + fragment.reactContext = reactContext; + fragment.viewTag = viewTag; - public NavViewFragment(ReactApplicationContext reactContext, int viewTag) { - this.reactContext = reactContext; - this.viewTag = viewTag; + return fragment; + } + + @SuppressLint("MissingPermission") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setNavigationUiEnabled(NavModule.getInstance().getNavigator() != null); + + getMapAsync( + googleMap -> { + mGoogleMap = googleMap; + + mMapViewController = new MapViewController(); + mMapViewController.initialize(googleMap, this::requireActivity); + + // Setup map listeners with the provided callback + mMapViewController.setupMapListeners(NavViewFragment.this); + + emitEvent("onMapReady", null); + + // Request layout to ensure fragment is properly sized + View fragmentView = getView(); + if (fragmentView != null) { + fragmentView.requestLayout(); + } + + setNavigationUiEnabled(NavModule.getInstance().getNavigator() != null); + addOnRecenterButtonClickedListener(onRecenterButtonClickedListener); + addPromptVisibilityChangedListener(onPromptVisibilityChangedListener); + }); } private final NavigationView.OnRecenterButtonClickedListener onRecenterButtonClickedListener = @@ -74,35 +112,6 @@ public void onVisibilityChanged(boolean isVisible) { } }; - private String style = ""; - - @SuppressLint("MissingPermission") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - setNavigationUiEnabled(NavModule.getInstance().getNavigator() != null); - - getMapAsync( - new OnMapReadyCallback() { - public void onMapReady(GoogleMap googleMap) { - mGoogleMap = googleMap; - - mMapViewController = new MapViewController(); - mMapViewController.initialize(googleMap, () -> requireActivity()); - - // Setup map listeners with the provided callback - mMapViewController.setupMapListeners(NavViewFragment.this); - - emitEvent("onMapReady", null); - - setNavigationUiEnabled(NavModule.getInstance().getNavigator() != null); - addOnRecenterButtonClickedListener(onRecenterButtonClickedListener); - addPromptVisibilityChangedListener(onPromptVisibilityChangedListener); - } - }); - } - public MapViewController getMapController() { return mMapViewController; } diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewLayout.java b/android/src/main/java/com/google/android/react/navsdk/NavViewLayout.java deleted file mode 100644 index 52179890..00000000 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewLayout.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - *

http://www.apache.org/licenses/LICENSE-2.0 - * - *

Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.react.navsdk; - -import android.content.Context; -import android.widget.FrameLayout; -import androidx.annotation.Nullable; -import com.google.android.libraries.navigation.StylingOptions; - -public class NavViewLayout extends FrameLayout { - private CustomTypes.FragmentType fragmentType; - private StylingOptions stylingOptions; - private boolean isFragmentCreated = false; - - public NavViewLayout(Context context) { - super(context); - } - - public void setFragmentType(CustomTypes.FragmentType type) { - this.fragmentType = type; - } - - @Nullable - public CustomTypes.FragmentType getFragmentType() { - return this.fragmentType; - } - - public void setStylingOptions(@Nullable StylingOptions options) { - this.stylingOptions = options; - } - - @Nullable - public StylingOptions getStylingOptions() { - return this.stylingOptions; - } - - public boolean isFragmentCreated() { - return this.isFragmentCreated; - } - - public void setFragmentCreated(boolean created) { - this.isFragmentCreated = created; - } -} diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java b/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java index b95bfd39..5ef60309 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java @@ -15,9 +15,9 @@ import static com.google.android.react.navsdk.Command.*; -import android.view.Choreographer; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -30,6 +30,8 @@ import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.GoogleMapOptions; +import com.google.android.libraries.navigation.StylingOptions; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; @@ -38,14 +40,13 @@ // NavViewManager is responsible for managing both the regular map fragment as well as the // navigation map view fragment. // -public class NavViewManager extends SimpleViewManager { +public class NavViewManager extends SimpleViewManager { public static final String REACT_CLASS = "NavViewManager"; private static NavViewManager instance; private final HashMap> fragmentMap = new HashMap<>(); - private final HashMap frameCallbackMap = new HashMap<>(); private ReactApplicationContext reactContext; @@ -57,6 +58,42 @@ public static synchronized NavViewManager getInstance(ReactApplicationContext re return instance; } + private boolean isFragmentCreated(int viewId) { + WeakReference weakReference = fragmentMap.get(viewId); + if (weakReference == null) { + return false; + } + IMapViewFragment fragment = weakReference.get(); + if (fragment == null) { + // Clean up the map entry if the fragment is not available anymore. + fragmentMap.remove(viewId); + return false; + } + return true; + } + + /** Builds GoogleMapOptions with all configured map settings. */ + @NonNull + private GoogleMapOptions buildGoogleMapOptions(ReadableMap mapInitializationOptions) { + GoogleMapOptions options = new GoogleMapOptions(); + if (mapInitializationOptions == null) { + return options; + } + + if (mapInitializationOptions.hasKey("mapId") && !mapInitializationOptions.isNull("mapId")) { + String mapIdFromOptions = mapInitializationOptions.getString("mapId"); + if (mapIdFromOptions != null && !mapIdFromOptions.isEmpty()) { + options.mapId(mapIdFromOptions); + } + } + + if (mapInitializationOptions.hasKey("mapType") && !mapInitializationOptions.isNull("mapType")) { + options.mapType(mapInitializationOptions.getInt("mapType")); + } + + return options; + } + @NonNull @Override public String getName() { @@ -67,25 +104,49 @@ public void setReactContext(ReactApplicationContext reactContext) { this.reactContext = reactContext; } - /** Return a NavViewLayout which will later hold the Fragment */ + /** Return a FrameLayout which will later hold the Fragment */ @NonNull @Override - public NavViewLayout createViewInstance(@NonNull ThemedReactContext reactContext) { - return new NavViewLayout(reactContext); + public FrameLayout createViewInstance(@NonNull ThemedReactContext reactContext) { + FrameLayout frameLayout = new FrameLayout(reactContext); + + // Add layout change listener to ensure proper layout of fragment + frameLayout.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + layoutFragmentInView(frameLayout); + }); + + return frameLayout; + } + + /** + * Ensures the fragment view is properly measured and laid out within its parent FrameLayout. This + * is necessary because React Native's layout system doesn't automatically propagate layout to + * native fragments. + */ + private void layoutFragmentInView(FrameLayout frameLayout) { + IMapViewFragment fragment = getFragmentForRoot(frameLayout); + if (fragment != null && fragment.isAdded()) { + View fragmentView = fragment.getView(); + if (fragmentView != null) { + int width = frameLayout.getMeasuredWidth(); + int height = frameLayout.getMeasuredHeight(); + + fragmentView.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + fragmentView.layout(0, 0, width, height); + } + } } /** Clean up fragment when React Native view is destroyed */ @Override - public void onDropViewInstance(@NonNull NavViewLayout view) { + public void onDropViewInstance(@NonNull FrameLayout view) { super.onDropViewInstance(view); int viewId = view.getId(); - Choreographer.FrameCallback frameCallback = frameCallbackMap.remove(viewId); - if (frameCallback != null) { - Choreographer.getInstance().removeFrameCallback(frameCallback); - } - FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity(); if (activity == null) return; @@ -102,31 +163,17 @@ public void onDropViewInstance(@NonNull NavViewLayout view) { } } - @ReactProp(name = "fragmentType") - public void setFragmentType(NavViewLayout view, @Nullable Integer fragmentTypeJsValue) { - if (fragmentTypeJsValue != null) { - CustomTypes.FragmentType fragmentType = - EnumTranslationUtil.getFragmentTypeFromJsValue(fragmentTypeJsValue); - view.setFragmentType(fragmentType); - createFragmentIfNeeded(view); - } - } - - @ReactProp(name = "stylingOptions") - public void setStylingOptions(NavViewLayout view, @Nullable ReadableMap options) { - if (options != null) { - view.setStylingOptions(new StylingOptionsBuilder.Builder(options.toHashMap()).build()); + @ReactProp(name = "mapInitializationOptions") + public void setMapInitializationOptions( + FrameLayout view, @NonNull ReadableMap mapInitializationOptions) { + int viewId = view.getId(); - if (view.isFragmentCreated()) { - IMapViewFragment fragment = getFragmentForRoot(view); - if (fragment != null) { - fragment.setStylingOptions(view.getStylingOptions()); - } - return; - } + // Don't create fragment if already exists + if (isFragmentCreated(viewId)) { + return; } - createFragmentIfNeeded(view); + scheduleFragmentTransaction(view, mapInitializationOptions); } /** Map the "create" command to an integer */ @@ -221,14 +268,14 @@ public void onNavigationReady() { for (WeakReference weakReference : fragmentMap.values()) { IMapViewFragment fragment = weakReference.get(); if (fragment instanceof INavViewFragment) { - fragment.applyStylingOptions(); + ((INavViewFragment) fragment).applyStylingOptions(); } } } @Override public void receiveCommand( - @NonNull NavViewLayout root, String commandId, @Nullable ReadableArray args) { + @NonNull FrameLayout root, String commandId, @Nullable ReadableArray args) { super.receiveCommand(root, commandId, args); int commandIdInt = Integer.parseInt(commandId); Command command = Command.find(commandIdInt); @@ -542,111 +589,62 @@ public Map getExportedCustomDirectEventTypeConstants() { return (Map) eventTypeConstants; } - private void createFragmentIfNeeded(NavViewLayout view) { - if (view.isFragmentCreated() || view.getFragmentType() == null) { - return; - } - - CustomTypes.FragmentType type = view.getFragmentType(); - - if (type == CustomTypes.FragmentType.MAP - || (type == CustomTypes.FragmentType.NAVIGATION && view.getStylingOptions() != null)) { - scheduleFragmentTransaction(view, type); - } - } - private void scheduleFragmentTransaction( - NavViewLayout root, CustomTypes.FragmentType fragmentType) { + @NonNull FrameLayout root, @NonNull ReadableMap mapInitializationOptions) { // Commit the fragment transaction after view is added to the view hierarchy. root.post( () -> { - if (root.isFragmentCreated()) { + if (isFragmentCreated(root.getId())) { return; } - commitFragmentTransaction(root, fragmentType); - root.setFragmentCreated(true); + commitFragmentTransaction(root, mapInitializationOptions); }); } /** Replace your React Native view with a custom fragment */ private void commitFragmentTransaction( - NavViewLayout view, CustomTypes.FragmentType fragmentType) { - - setupLayout(view); + @NonNull FrameLayout view, @NonNull ReadableMap mapInitializationOptions) { FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity(); if (activity == null) return; - int viewId = view.getId(); Fragment fragment; + CustomTypes.FragmentType fragmentType = + EnumTranslationUtil.getFragmentTypeFromJsValue( + mapInitializationOptions.getInt("fragmentType")); + + GoogleMapOptions googleMapOptions = buildGoogleMapOptions(mapInitializationOptions); + if (fragmentType == CustomTypes.FragmentType.MAP) { - MapViewFragment mapFragment = new MapViewFragment(reactContext, viewId); - fragmentMap.put(viewId, new WeakReference(mapFragment)); - fragment = mapFragment; + fragment = MapViewFragment.newInstance(reactContext, viewId, googleMapOptions); } else { - NavViewFragment navFragment = new NavViewFragment(reactContext, viewId); - if (view.getStylingOptions() != null) { - navFragment.setStylingOptions(view.getStylingOptions()); + NavViewFragment navFragment = + NavViewFragment.newInstance(reactContext, viewId, googleMapOptions); + + if (mapInitializationOptions.hasKey("navigationStylingOptions") + && !mapInitializationOptions.isNull("navigationStylingOptions")) { + ReadableMap stylingOptionsMap = mapInitializationOptions.getMap("navigationStylingOptions"); + StylingOptions stylingOptions = + new StylingOptionsBuilder.Builder(stylingOptionsMap.toHashMap()).build(); + navFragment.setStylingOptions(stylingOptions); } - fragmentMap.put(viewId, new WeakReference(navFragment)); + fragment = navFragment; } + fragmentMap.put(viewId, new WeakReference((IMapViewFragment) fragment)); + activity .getSupportFragmentManager() .beginTransaction() .replace(viewId, fragment, String.valueOf(viewId)) .commit(); - } - - /** - * Set up the layout for each frame. This official RN way to do this, but a bit hacky, and should - * be changed when better solution is found. - */ - public void setupLayout(NavViewLayout view) { - int viewId = view.getId(); - - // Remove any existing callback for this viewId - Choreographer.FrameCallback existingCallback = frameCallbackMap.get(viewId); - if (existingCallback != null) { - Choreographer.getInstance().removeFrameCallback(existingCallback); - } - - Choreographer.FrameCallback frameCallback = - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - // Check if this view still has a valid fragment before proceeding - IMapViewFragment fragment = getFragmentForViewId(viewId); - if (fragment != null) { - manuallyLayoutChildren(view); - view.getViewTreeObserver().dispatchOnGlobalLayout(); - Choreographer.getInstance().postFrameCallback(this); - } else { - // Fragment no longer exists or was garbage collected, remove callback and stop - frameCallbackMap.remove(viewId); - } - } - }; - - frameCallbackMap.put(viewId, frameCallback); - Choreographer.getInstance().postFrameCallback(frameCallback); - } - /** Layout all children properly */ - public void manuallyLayoutChildren(NavViewLayout view) { - IMapViewFragment fragment = getFragmentForRoot(view); - if (fragment != null && fragment.isAdded()) { - View childView = fragment.getView(); - if (childView != null) { - childView.measure( - View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), View.MeasureSpec.EXACTLY)); - childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); - } - } + // Trigger layout after fragment is added + // Post to ensure fragment transaction is complete + view.post(() -> layoutFragmentInView(view)); } public GoogleMap getGoogleMap(int viewId) { diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7781511e..77d1a260 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -147,7 +147,7 @@ dependencies { implementation "androidx.car.app:app-projected:1.4.0" // Include the Google Navigation SDK. - implementation 'com.google.android.libraries.navigation:navigation:7.0.0' + implementation 'com.google.android.libraries.navigation:navigation:7.1.0' } secrets { diff --git a/example/src/App.tsx b/example/src/App.tsx index 61dcebd4..858bc5eb 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -14,21 +14,23 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { NavigationContainer, useIsFocused, - useNavigation, + useNavigation as useAppNavigation, type NavigationProp, } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; -import { View, Button, StyleSheet } from 'react-native'; +import { View, Button, Text } from 'react-native'; +import { CommonStyles } from './styles/components'; import NavigationScreen from './screens/NavigationScreen'; import MultipleMapsScreen from './screens/MultipleMapsScreen'; +import MapIdScreen from './screens/MapIdScreen'; import { NavigationProvider, TaskRemovedBehavior, - type TermsAndConditionsDialogOptions, + useNavigation, } from '@googlemaps/react-native-navigation-sdk'; import IntegrationTestsScreen from './screens/IntegrationTestsScreen'; @@ -36,6 +38,7 @@ export type ScreenNames = [ 'Home', 'Navigation', 'Multiple maps', + 'Map ID', 'Integration tests', ]; @@ -43,28 +46,56 @@ export type RootStackParamList = Record; export type StackNavigation = NavigationProp; const HomeScreen = () => { - const { navigate } = useNavigation(); + const { navigate } = useAppNavigation(); const isFocused = useIsFocused(); + const [sdkVersion, setSdkVersion] = useState(''); + + const { navigationController } = useNavigation(); + + useEffect(() => { + const fetchVersion = async () => { + try { + const version = await navigationController.getNavSDKVersion(); + setSdkVersion(version); + } catch (error) { + console.error('Failed to fetch SDK version:', error); + setSdkVersion('Unknown'); + } + }; + + fetchVersion(); + }, [navigationController]); return ( - + + {/* SDK Version Display */} + + + Navigation SDK Version: {sdkVersion || 'Loading...'} + + {/* Spacer */} - - +