1515
1616import static com .google .android .react .navsdk .Command .*;
1717
18+ import android .view .Choreographer ;
1819import android .view .View ;
1920import android .view .ViewGroup ;
2021import android .widget .FrameLayout ;
4344public class NavViewManager extends SimpleViewManager <FrameLayout > {
4445
4546 public static final String REACT_CLASS = "NavViewManager" ;
46-
4747 private static NavViewManager instance ;
4848
4949 private final HashMap <Integer , WeakReference <IMapViewFragment >> fragmentMap = new HashMap <>();
50+ private final HashMap <Integer , Choreographer .FrameCallback > frameCallbackMap = new HashMap <>();
5051
5152 // Cache the latest options per view so deferred fragment creation uses fresh values.
5253 private final HashMap <Integer , ReadableMap > mapOptionsCache = new HashMap <>();
@@ -118,13 +119,6 @@ public void setReactContext(ReactApplicationContext reactContext) {
118119 @ Override
119120 public FrameLayout createViewInstance (@ NonNull ThemedReactContext reactContext ) {
120121 FrameLayout frameLayout = new FrameLayout (reactContext );
121-
122- // Add layout change listener to ensure proper layout of fragment
123- frameLayout .addOnLayoutChangeListener (
124- (v , left , top , right , bottom , oldLeft , oldTop , oldRight , oldBottom ) -> {
125- layoutFragmentInView (frameLayout );
126- });
127-
128122 return frameLayout ;
129123 }
130124
@@ -133,20 +127,57 @@ public FrameLayout createViewInstance(@NonNull ThemedReactContext reactContext)
133127 * is necessary because React Native's layout system doesn't automatically propagate layout to
134128 * native fragments.
135129 */
136- private void layoutFragmentInView (FrameLayout frameLayout ) {
137- IMapViewFragment fragment = getFragmentForRoot (frameLayout );
138- if (fragment != null && fragment .isAdded ()) {
139- View fragmentView = fragment .getView ();
140- if (fragmentView != null ) {
141- int width = frameLayout .getMeasuredWidth ();
142- int height = frameLayout .getMeasuredHeight ();
143-
144- fragmentView .measure (
145- View .MeasureSpec .makeMeasureSpec (width , View .MeasureSpec .EXACTLY ),
146- View .MeasureSpec .makeMeasureSpec (height , View .MeasureSpec .EXACTLY ));
147- fragmentView .layout (0 , 0 , width , height );
148- }
130+ private void layoutFragmentInView (FrameLayout frameLayout , @ Nullable IMapViewFragment fragment ) {
131+ if (fragment == null || !fragment .isAdded ()) {
132+ return ;
149133 }
134+
135+ int width = frameLayout .getWidth ();
136+ int height = frameLayout .getHeight ();
137+ if (width == 0 || height == 0 ) {
138+ return ;
139+ }
140+
141+ View fragmentView = fragment .getView ();
142+ if (fragmentView == null ) {
143+ return ;
144+ }
145+
146+ fragmentView .measure (
147+ View .MeasureSpec .makeMeasureSpec (width , View .MeasureSpec .EXACTLY ),
148+ View .MeasureSpec .makeMeasureSpec (height , View .MeasureSpec .EXACTLY ));
149+ fragmentView .layout (0 , 0 , width , height );
150+ }
151+
152+ /**
153+ * Starts a per-frame layout loop similar to the older implementation to ensure the fragment view
154+ * stays in sync with its container size.
155+ */
156+ private void startLayoutLoop (FrameLayout view ) {
157+ int viewId = view .getId ();
158+
159+ // Remove existing callback if present
160+ Choreographer .FrameCallback existing = frameCallbackMap .get (viewId );
161+ if (existing != null ) {
162+ Choreographer .getInstance ().removeFrameCallback (existing );
163+ }
164+
165+ Choreographer .FrameCallback frameCallback =
166+ new Choreographer .FrameCallback () {
167+ @ Override
168+ public void doFrame (long frameTimeNanos ) {
169+ IMapViewFragment fragment = getFragmentForViewId (viewId );
170+ if (fragment != null ) {
171+ layoutFragmentInView (view , fragment );
172+ Choreographer .getInstance ().postFrameCallback (this );
173+ } else {
174+ frameCallbackMap .remove (viewId );
175+ }
176+ }
177+ };
178+
179+ frameCallbackMap .put (viewId , frameCallback );
180+ Choreographer .getInstance ().postFrameCallback (frameCallback );
150181 }
151182
152183 /** Clean up fragment when React Native view is destroyed */
@@ -156,6 +187,11 @@ public void onDropViewInstance(@NonNull FrameLayout view) {
156187
157188 int viewId = view .getId ();
158189
190+ Choreographer .FrameCallback frameCallback = frameCallbackMap .remove (viewId );
191+ if (frameCallback != null ) {
192+ Choreographer .getInstance ().removeFrameCallback (frameCallback );
193+ }
194+
159195 FragmentActivity activity = (FragmentActivity ) reactContext .getCurrentActivity ();
160196 if (activity == null ) return ;
161197
@@ -278,7 +314,9 @@ public void onNavigationReady() {
278314 for (WeakReference <IMapViewFragment > weakReference : fragmentMap .values ()) {
279315 IMapViewFragment fragment = weakReference .get ();
280316 if (fragment instanceof INavViewFragment ) {
281- ((INavViewFragment ) fragment ).applyStylingOptions ();
317+ INavViewFragment navFragment = (INavViewFragment ) fragment ;
318+ navFragment .setNavigationUiEnabled (true );
319+ navFragment .applyStylingOptions ();
282320 }
283321 }
284322 }
@@ -604,16 +642,29 @@ private void scheduleFragmentTransaction(
604642 @ NonNull FrameLayout root , @ NonNull ReadableMap mapOptions ) {
605643
606644 // Commit the fragment transaction after view is added to the view hierarchy.
607- root .post (
608- () -> {
609- int viewId = root .getId ();
610- if (isFragmentCreated (viewId )) {
611- return ;
612- }
613- ReadableMap latestOptions = mapOptionsCache .get (viewId );
614- ReadableMap optionsToUse = latestOptions != null ? latestOptions : mapOptions ;
615- commitFragmentTransaction (root , optionsToUse );
616- });
645+ root .post (() -> tryCommitFragmentTransaction (root , mapOptions ));
646+ }
647+
648+ /** Attempt to create/attach the fragment once the parent view has a real size. */
649+ private void tryCommitFragmentTransaction (
650+ @ NonNull FrameLayout root , @ NonNull ReadableMap initialMapOptions ) {
651+ int viewId = root .getId ();
652+ if (isFragmentCreated (viewId )) {
653+ return ;
654+ }
655+
656+ ReadableMap latestOptions = mapOptionsCache .get (viewId );
657+ ReadableMap optionsToUse = latestOptions != null ? latestOptions : initialMapOptions ;
658+
659+ int width = root .getWidth ();
660+ int height = root .getHeight ();
661+ if (width == 0 || height == 0 ) {
662+ // Wait for layout to provide a size, then retry without the per-frame choreographer loop.
663+ root .post (() -> tryCommitFragmentTransaction (root , optionsToUse ));
664+ return ;
665+ }
666+
667+ commitFragmentTransaction (root , optionsToUse );
617668 }
618669
619670 private void updateMapOptionValues (int viewId , @ NonNull ReadableMap mapOptions ) {
@@ -654,14 +705,18 @@ private void commitFragmentTransaction(
654705 if (activity == null ) return ;
655706 int viewId = view .getId ();
656707 Fragment fragment ;
708+ IMapViewFragment mapViewFragment ;
657709
658710 CustomTypes .MapViewType mapViewType =
659711 EnumTranslationUtil .getMapViewTypeFromJsValue (mapOptions .getInt ("mapViewType" ));
660712
661713 GoogleMapOptions googleMapOptions = buildGoogleMapOptions (mapOptions );
662714
663715 if (mapViewType == CustomTypes .MapViewType .MAP ) {
664- fragment = MapViewFragment .newInstance (reactContext , viewId , googleMapOptions );
716+ MapViewFragment mapFragment =
717+ MapViewFragment .newInstance (reactContext , viewId , googleMapOptions );
718+ fragment = mapFragment ;
719+ mapViewFragment = mapFragment ;
665720 } else {
666721 NavViewFragment navFragment =
667722 NavViewFragment .newInstance (reactContext , viewId , googleMapOptions );
@@ -682,19 +737,23 @@ private void commitFragmentTransaction(
682737 }
683738
684739 fragment = navFragment ;
740+ mapViewFragment = navFragment ;
685741 }
686742
687- fragmentMap .put (viewId , new WeakReference <IMapViewFragment >(( IMapViewFragment ) fragment ));
743+ fragmentMap .put (viewId , new WeakReference <IMapViewFragment >(mapViewFragment ));
688744
689745 activity
690746 .getSupportFragmentManager ()
691747 .beginTransaction ()
692748 .replace (viewId , fragment , String .valueOf (viewId ))
693749 .commit ();
694750
751+ // Start per-frame layout loop to keep fragment sized correctly.
752+ startLayoutLoop (view );
753+
695754 // Trigger layout after fragment is added
696755 // Post to ensure fragment transaction is complete
697- view .post (() -> layoutFragmentInView (view ));
756+ view .post (() -> layoutFragmentInView (view , mapViewFragment ));
698757 }
699758
700759 public GoogleMap getGoogleMap (int viewId ) {
0 commit comments