3535import com .google .android .libraries .navigation .StylingOptions ;
3636import java .lang .ref .WeakReference ;
3737import java .util .HashMap ;
38+ import java .util .HashSet ;
3839import java .util .Map ;
3940import java .util .Objects ;
4041
@@ -52,6 +53,9 @@ public class NavViewManager extends SimpleViewManager<FrameLayout> {
5253 // Cache the latest options per view so deferred fragment creation uses fresh values.
5354 private final HashMap <Integer , ReadableMap > mapOptionsCache = new HashMap <>();
5455
56+ // Track views with pending fragment creation attempts.
57+ private final HashSet <Integer > pendingFragments = new HashSet <>();
58+
5559 private ReactApplicationContext reactContext ;
5660
5761 public static synchronized NavViewManager getInstance (ReactApplicationContext reactContext ) {
@@ -187,6 +191,9 @@ public void onDropViewInstance(@NonNull FrameLayout view) {
187191
188192 int viewId = view .getId ();
189193
194+ pendingFragments .remove (viewId );
195+ mapOptionsCache .remove (viewId );
196+
190197 Choreographer .FrameCallback frameCallback = frameCallbackMap .remove (viewId );
191198 if (frameCallback != null ) {
192199 Choreographer .getInstance ().removeFrameCallback (frameCallback );
@@ -196,7 +203,6 @@ public void onDropViewInstance(@NonNull FrameLayout view) {
196203 if (activity == null ) return ;
197204
198205 WeakReference <IMapViewFragment > weakReference = fragmentMap .remove (viewId );
199- mapOptionsCache .remove (viewId );
200206 if (weakReference != null ) {
201207 IMapViewFragment fragment = weakReference .get ();
202208 if (fragment != null && fragment .isAdded ()) {
@@ -219,7 +225,10 @@ public void setMapOptions(FrameLayout view, @NonNull ReadableMap mapOptions) {
219225 return ;
220226 }
221227
222- scheduleFragmentTransaction (view , mapOptions );
228+ if (!pendingFragments .contains (viewId )) {
229+ pendingFragments .add (viewId );
230+ scheduleFragmentTransaction (view );
231+ }
223232 }
224233
225234 /** Map the "create" command to an integer */
@@ -641,33 +650,43 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
641650 return (Map ) eventTypeConstants ;
642651 }
643652
644- private void scheduleFragmentTransaction (
645- @ NonNull FrameLayout root , @ NonNull ReadableMap mapOptions ) {
646-
647- // Commit the fragment transaction after view is added to the view hierarchy.
648- root .post (() -> tryCommitFragmentTransaction (root , mapOptions ));
653+ private void scheduleFragmentTransaction (@ NonNull FrameLayout root ) {
654+ root .post (() -> tryCommitFragmentTransaction (root ));
649655 }
650656
651657 /** Attempt to create/attach the fragment once the parent view has a real size. */
652- private void tryCommitFragmentTransaction (
653- @ NonNull FrameLayout root , @ NonNull ReadableMap initialMapOptions ) {
658+ private void tryCommitFragmentTransaction (@ NonNull FrameLayout root ) {
654659 int viewId = root .getId ();
660+
655661 if (isFragmentCreated (viewId )) {
656662 return ;
657663 }
658664
659- ReadableMap latestOptions = mapOptionsCache .get (viewId );
660- ReadableMap optionsToUse = latestOptions != null ? latestOptions : initialMapOptions ;
665+ // If pendingFragments does not contain viewId, view was dropped and we should abort retry loop.
666+ if (!pendingFragments .contains (viewId )) {
667+ return ;
668+ }
669+
670+ ReadableMap mapOptions = mapOptionsCache .get (viewId );
671+ if (mapOptions == null ) {
672+ return ;
673+ }
674+
675+ // If view is not attached to window, retry later.
676+ if (!root .isAttachedToWindow ()) {
677+ scheduleFragmentTransaction (root );
678+ return ;
679+ }
661680
681+ // Wait for layout to provide a size
662682 int width = root .getWidth ();
663683 int height = root .getHeight ();
664684 if (width == 0 || height == 0 ) {
665- // Wait for layout to provide a size, then retry without the per-frame choreographer loop.
666- root .post (() -> tryCommitFragmentTransaction (root , optionsToUse ));
685+ scheduleFragmentTransaction (root );
667686 return ;
668687 }
669688
670- commitFragmentTransaction (root , optionsToUse );
689+ commitFragmentTransaction (root , mapOptions );
671690 }
672691
673692 private void updateMapOptionValues (int viewId , @ NonNull ReadableMap mapOptions ) {
@@ -693,26 +712,33 @@ private void updateMapOptionValues(int viewId, @NonNull ReadableMap mapOptions)
693712 }
694713
695714 if (fragment instanceof INavViewFragment && mapOptions .hasKey ("navigationNightMode" )) {
696- int nightMode =
715+ int jsValue =
697716 mapOptions .isNull ("navigationNightMode" ) ? 0 : mapOptions .getInt ("navigationNightMode" );
698717 ((INavViewFragment ) fragment )
699- .setNightModeOption (EnumTranslationUtil .getForceNightModeFromJsValue (nightMode ));
718+ .setNightModeOption (EnumTranslationUtil .getForceNightModeFromJsValue (jsValue ));
700719 }
701720 }
702721
703- /** Replace your React Native view with a custom fragment */
722+ /**
723+ * Attaches the appropriate Map or Navigation fragment to the given parent view. Uses
724+ * commitNowAllowingStateLoss for immediate attachment. If FragmentManager is busy, retries
725+ * asynchronously by calling scheduleFragmentTransaction.
726+ */
704727 private void commitFragmentTransaction (
705728 @ NonNull FrameLayout view , @ NonNull ReadableMap mapOptions ) {
706729
707730 FragmentActivity activity = (FragmentActivity ) reactContext .getCurrentActivity ();
708- if (activity == null ) return ;
731+ if (activity == null || activity .isFinishing ()) {
732+ return ;
733+ }
734+
709735 int viewId = view .getId ();
736+ String fragmentTag = String .valueOf (viewId );
710737 Fragment fragment ;
711738 IMapViewFragment mapViewFragment ;
712739
713740 CustomTypes .MapViewType mapViewType =
714741 EnumTranslationUtil .getMapViewTypeFromJsValue (mapOptions .getInt ("mapViewType" ));
715-
716742 GoogleMapOptions googleMapOptions = buildGoogleMapOptions (mapOptions );
717743
718744 if (mapViewType == CustomTypes .MapViewType .MAP ) {
@@ -723,12 +749,10 @@ private void commitFragmentTransaction(
723749 } else {
724750 NavViewFragment navFragment =
725751 NavViewFragment .newInstance (reactContext , viewId , googleMapOptions );
726- Integer nightMode = null ;
727- if (mapOptions .hasKey ("navigationNightMode" )) {
728- int jsValue =
729- mapOptions .isNull ("navigationNightMode" ) ? 0 : mapOptions .getInt ("navigationNightMode" );
730- nightMode = EnumTranslationUtil .getForceNightModeFromJsValue (jsValue );
731- navFragment .setNightModeOption (nightMode );
752+
753+ if (mapOptions .hasKey ("navigationNightMode" ) && !mapOptions .isNull ("navigationNightMode" )) {
754+ int jsValue = mapOptions .getInt ("navigationNightMode" );
755+ navFragment .setNightModeOption (EnumTranslationUtil .getForceNightModeFromJsValue (jsValue ));
732756 }
733757
734758 if (mapOptions .hasKey ("navigationStylingOptions" )
@@ -743,19 +767,32 @@ private void commitFragmentTransaction(
743767 mapViewFragment = navFragment ;
744768 }
745769
746- fragmentMap .put (viewId , new WeakReference <IMapViewFragment >(mapViewFragment ));
770+ // Execute Transaction
771+ try {
772+ activity
773+ .getSupportFragmentManager ()
774+ .beginTransaction ()
775+ .replace (viewId , fragment , fragmentTag )
776+ .commitNowAllowingStateLoss ();
777+ } catch (IllegalStateException e ) {
778+ // FragmentManager is busy or Activity state is invalid.
779+ // re-schedule the transaction.
780+ scheduleFragmentTransaction (view );
781+ return ;
782+ } catch (Exception e ) {
783+ // For other unrecoverable errors, simply abort.
784+ // Most likely the activity is finishing or destroyed.
785+ return ;
786+ }
747787
748- activity
749- .getSupportFragmentManager ()
750- .beginTransaction ()
751- .replace (viewId , fragment , String .valueOf (viewId ))
752- .commit ();
788+ // Fragment created successfully, update state.
789+ pendingFragments .remove (viewId );
790+ mapOptionsCache .remove (viewId );
791+ fragmentMap .put (viewId , new WeakReference <>(mapViewFragment ));
753792
754793 // Start per-frame layout loop to keep fragment sized correctly.
755794 startLayoutLoop (view );
756-
757- // Trigger layout after fragment is added
758- // Post to ensure fragment transaction is complete
795+ // Trigger layout after fragment transaction is done.
759796 view .post (() -> layoutFragmentInView (view , mapViewFragment ));
760797 }
761798
0 commit comments