Skip to content

Android: App crashes when dynamically adding/removing UrlTile components from MapView #5807

@isaacrowntree

Description

@isaacrowntree

Summary

On Android, dynamically toggling UrlTile components on and off within a MapView causes the application to crash. This occurs even when toggling slowly (not just rapid toggling). The crash happens when UrlTile components are conditionally rendered based on state changes, specifically when regions are enabled/disabled in an offline maps feature.

Reproducible sample code

import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import MapView, { UrlTile } from 'react-native-maps';

const INITIAL_REGION = {
  latitude: -25.2744,
  longitude: 133.7751,
  latitudeDelta: 20,
  longitudeDelta: 20,
};

export default function App() {
  const [showTiles, setShowTiles] = useState(true);

  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        initialRegion={INITIAL_REGION}
      >
        {showTiles && (
          <UrlTile
            key="offline-tiles"
            urlTemplate="file:///path/to/tiles/{z}/{x}/{y}.png"
            tileSize={256}
            zIndex={1000}
            shouldReplaceMapContent={true}
          />
        )}
      </MapView>
      
      <View style={styles.buttonContainer}>
        <Button
          title={showTiles ? "Disable Tiles" : "Enable Tiles"}
          onPress={() => setShowTiles(!showTiles)}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
  buttonContainer: {
    position: 'absolute',
    bottom: 50,
    left: 20,
    right: 20,
  },
});

Steps to reproduce

  1. Create a React Native app with react-native-maps
  2. Add a MapView component with conditional UrlTile rendering
  3. Add a button/toggle to change the state that controls UrlTile visibility
  4. Run the app on an Android device/emulator
  5. Important: Pan the map so it's viewing the geographic region where the UrlTile covers (e.g., if using Australian tiles, view Australia)
  6. Toggle the UrlTile on/off 2-3 times slowly (wait 2-3 seconds between toggles)
  7. App crashes on Android

Critical Finding: The crash only occurs when the MapView is actively displaying the region covered by the UrlTile. If the map is panned to a different location (e.g., viewing Europe when toggling Australian tiles), toggling works without crashing. This suggests the crash is related to:

  • Active tile loading/rendering from the UrlTile
  • Native tile layer actively being displayed
  • Memory/resource cleanup while tiles are in use

Additional Notes: The issue occurs whether using:

  • shouldReplaceMapContent={true} or false
  • Single UrlTile or multiple UrlTile components
  • File paths or HTTP URLs in urlTemplate

Expected result

When toggling the state that controls UrlTile rendering:

  1. UrlTile should be cleanly removed from the MapView when state becomes false
  2. UrlTile should be cleanly added to the MapView when state becomes true
  3. App should remain stable and responsive
  4. No crashes or native errors should occur
  5. Behavior should match iOS (which works correctly)

Actual result

  1. After 1-3 toggles, the app crashes on Android
  2. No JavaScript error is caught
  3. The crash appears to be in the native layer
  4. The crash occurs even with slow, deliberate toggling (not rapid state changes)
  5. iOS platform works without any issues

React Native Maps Version

1.26.18

What platforms are you seeing the problem on?

Android

React Native Version

0.81.5

What version of Expo are you using?

SDK 53

Device(s)

Android Simulator, Android Hardware (all!)

Additional information

Expo SDK 54 actually, not 53.

Potential Root Cause

Based on the stack trace and testing, the issue is a synchronization bug in MapView.addFeature() when using React Native's Fabric renderer while tiles are actively being rendered. Specifically:

  1. Index Mismatch: When UrlTile components are removed and re-added, React's Fabric reconciler sends instructions to add a child at a specific index (e.g., index 1)
  2. Stale Internal State: The native MapView.addFeature() method's internal ArrayList doesn't reflect the same state as Fabric expects
  3. ArrayList Corruption: The mismatch causes ArrayList.add(index) to throw IndexOutOfBoundsException because it tries to add at index 1 when the list size is 0
  4. Active Tile Rendering Race Condition: The crash only occurs when tiles are actively being loaded/rendered in the viewport, suggesting the native tile layer cleanup happens asynchronously while Fabric tries to re-add the component

This appears to be specific to:

  • Fabric renderer (New Architecture)
  • Dynamic child removal/addition (toggling UrlTile components)
  • Active tile rendering (map viewport showing the tile region)
  • Android only (iOS properly handles the lifecycle)

Key Finding: If the map is panned away from the tile coverage area before toggling, the crash does not occur. This confirms the issue is related to cleaning up actively-rendered tiles while Fabric is reconciling the view hierarchy.

The bug is in com.rnmaps.maps.MapView.addFeature(MapView.java:1214) where it doesn't properly handle index validation or doesn't correctly sync with Fabric's view hierarchy state during active tile operations.

Related Issues

This may be related to general Android MapView stability issues with dynamic child components. Similar patterns with Marker components work fine, suggesting UrlTile has specific lifecycle management issues on Android.

Stack Trace

11-12 12:52:02.557  8224  8224 E DevLauncher: addViewAt: failed to insert view [2452] into parent [636] at index 1
11-12 12:52:02.557  8224  8224 E DevLauncher:
11-12 12:52:02.557  8224  8224 E DevLauncher: Index: 1, Size: 0
11-12 12:52:02.557  8224  8224 E DevLauncher: java.lang.IllegalStateException: addViewAt: failed to insert view [2452] into parent [636] at index 1
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.java:410)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(IntBufferBatchMountItem.kt:111)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	... 22 more
11-12 12:52:02.557  8224  8224 E DevLauncher: Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at java.util.ArrayList.rangeCheckForAdd(ArrayList.java:845)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at java.util.ArrayList.add(ArrayList.java:517)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.rnmaps.maps.MapView.addFeature(MapView.java:1214)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.rnmaps.fabric.MapViewManager.addView(MapViewManager.java:320)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.rnmaps.fabric.MapViewManager.addView(MapViewManager.java:43)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.java:407)
11-12 12:52:02.557  8224  8224 E DevLauncher: 	... 22 more

Root Cause Identified:

The crash occurs in com.rnmaps.maps.MapView.addFeature(MapView.java:1214) when trying to add a child view (UrlTile) at index 1 to an ArrayList that has size 0. This indicates a synchronization issue where:

  1. React's Fabric reconciler believes there are already children in the MapView
  2. The native MapView's internal ArrayList of features is empty
  3. When trying to insert the UrlTile at index 1 (second position), it throws IndexOutOfBoundsException

This suggests that when UrlTile components are removed and then re-added, the native MapView is not properly maintaining its internal child view list, causing index mismatches between React's virtual DOM and the native view hierarchy.

Suggested Fix

The fix should be implemented in com.rnmaps.maps.MapView.addFeature() (line 1214) to:

  1. Add Index Validation: Before calling ArrayList.add(index), validate that the index is within valid bounds (0 to size)
  2. Fallback to Append: If index is out of bounds, append to the end of the list instead of crashing
  3. Sync with Fabric State: Ensure the internal feature list properly reflects Fabric's view hierarchy expectations
  4. Proper Cleanup on Remove: When child views are removed via Fabric, ensure the internal ArrayList is updated correctly

Potential Java Code Fix:

// In MapView.java, around line 1214
public void addFeature(View child, int index) {
  // Validate index bounds to prevent IndexOutOfBoundsException
  if (index < 0 || index > features.size()) {
    // Fallback: add to end if index is invalid
    index = features.size();
  }
  features.add(index, child);
  // ... rest of implementation
}

This would match the stable behavior already present on iOS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions