maps icon indicating copy to clipboard operation
maps copied to clipboard

[Bug]: ShapeSource.getClusterLeaves crash on iOS (NOBRIDGE mode)

Open villemuittari opened this issue 1 year ago • 8 comments

Mapbox Implementation

Mapbox

Mapbox Version

11.3.0

React Native Version

0.75.2

Platform

iOS

@rnmapbox/maps version

10.1.31

Standalone component to reproduce

import React, {useRef} from 'react';
import {Button} from 'react-native';
import {
  Images,
  MapView,
  ShapeSource,
  SymbolLayer,
  CircleLayer,
  Camera,
} from '@rnmapbox/maps';

const styles = {
  mapView: {width: '100%', height: 500},
  cluster: {
    circleRadiusTransition: {duration: 5000, delay: 0},
    circleColor: '#ff0000',
  },
};

const features = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      id: 'a-feature',
      properties: {
        icon: 'example',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.00597, 40.71427],
      },
    },
    {
      type: 'Feature',
      id: 'b-feature',
      properties: {
        icon: 'example',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.001097, 40.71527],
      },
    },
    {
      type: 'Feature',
      id: 'c-feature',
      properties: {
        icon: 'example',
      },
      geometry: {
        type: 'Point',
        coordinates: [-74.00697, 40.72427],
      },
    },
  ],
};

const BugReportExample = () => {
  const mapView = useRef<MapView>(null);
  const shapeSourceMarkers = useRef<ShapeSource>(null);

  const getVisibleMarkers = async () => {
    let visibleMarkers: any[] = [];
    const arrFeatures = await mapView.current?.queryRenderedFeaturesInRect(
      [],
      [],
      ['marker', 'cluster'],
    );
    if (!arrFeatures) {
      return visibleMarkers;
    }
    for (let index = 0; index < arrFeatures.features.length; index++) {
      const feature = arrFeatures.features[index];
      const properties = feature.properties;
      if (!properties) {
        continue;
      }

      if (properties.cluster) {
        try {
          // ====> THE FOLLOWING LINE CRASHES THE WHOLE APP <====
          const collection = await shapeSourceMarkers.current?.getClusterLeaves(
            feature,
            properties.point_count,
            0,
          );
          collection.features.forEach((f: any) => {
            visibleMarkers.push(f);
          });
        } catch (error: any) {
          console.debug('error', error);
        }
      } else {
        visibleMarkers.push(feature);
      }
    }
    console.debug('visibleMarkers', visibleMarkers);
    return visibleMarkers;
  };

  const circleLayerStyle = {
    ...styles.cluster,
    ...{circleRadius: 15},
  };

  return (
    <>
      <MapView style={styles.mapView} ref={mapView}>
        <Camera
          defaultSettings={{
            centerCoordinate: [-74.001097, 40.71527],
            zoomLevel: 10,
          }}
        />
        <Images images={{example: {uri: 'https://27crags-sandbox.s3.amazonaws.com/v6-icon.png'}}} />
        <ShapeSource
          id={'shape-source-markers'}
          ref={shapeSourceMarkers}
          shape={features}
          cluster={true}
          clusterRadius={30}
          clusterMaxZoomLevel={19}>
          <SymbolLayer
            id="marker"
            style={{
              iconImage: ['get', 'icon'],
              iconAllowOverlap: false,
              iconSize: 0.5,
            }}
            slot={'middle'}
            filter={['!', ['has', 'point_count']]}
          />
          <SymbolLayer
            id="pointCount"
            style={{
              textField: ['format', ['concat', ['get', 'point_count']]],
              textSize: 12,
              textPitchAlignment: 'viewport',
              textAllowOverlap: false,
            }}
          />
          <CircleLayer
            id={'cluster'}
            belowLayerID="pointCount"
            style={circleLayerStyle}
            slot={'bottom'}
            filter={['has', 'point_count']}
          />
        </ShapeSource>
      </MapView>
      <Button
        title="Count all items inside clusters"
        onPress={() => {
          getVisibleMarkers().then(visibleMarkers => {
            console.debug('visibleMarkers', visibleMarkers);
          });
        }}
      />
    </>
  );
};

export default BugReportExample;

Observed behavior and steps to reproduce

When trying to get clusterLeaves using the method getClusterLeaves() of ShapeSource the app crashes immediately.

0   libobjc.A.dylib               	       0x18005d44c objc_retain + 8
1   libobjc.A.dylib               	       0x1800856c0 objc_storeStrong + 44
2   RNLatest                      	       0x101870b78 -[RNMBXShapeSourceModule getClusterLeaves:featureJSON:number:offset:resolve:reject:] + 120 (RNMBXShapeSourceModule.mm:56)
3   CoreFoundation                	       0x1804b4720 __invoking___ + 144
4   CoreFoundation                	       0x1804b1a84 -[NSInvocation invoke] + 276
5   CoreFoundation                	       0x1804b1d1c -[NSInvocation invokeWithTarget:] + 60
6   RNLatest                      	       0x1012bc5c8 invocation function for block in facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*) + 240 (RCTTurboModule.mm:347)
7   RNLatest                      	       0x1012d2d90 facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2::operator()() const + 96 (RCTTurboModule.mm:380)
8   RNLatest                      	       0x1012d2d24 decltype(std::declval<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&>()()) std::__1::__invoke[abi:ue170006]<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&>(facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&) + 24 (invoke.h:340)
9   RNLatest                      	       0x1012d2cdc void std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ue170006]<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&>(facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&) + 24 (invoke.h:415)
10  RNLatest                      	       0x1012d2cb8 std::__1::__function::__alloc_func<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2, std::__1::allocator<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2>, void ()>::operator()[abi:ue170006]() + 28 (function.h:193)
11  RNLatest                      	       0x1012d1a1c std::__1::__function::__func<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2, std::__1::allocator<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2>, void ()>::operator()() + 28 (function.h:364)
12  RNLatest                      	       0x100e7b534 std::__1::__function::__value_func<void ()>::operator()[abi:ue170006]() const + 68 (function.h:518)
13  RNLatest                      	       0x100e7b454 std::__1::function<void ()>::operator()() const + 24 (function.h:1169)
14  RNLatest                      	       0x1012e25e4 invocation function for block in (anonymous namespace)::ModuleNativeMethodCallInvoker::invokeAsync(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::function<void ()>&&) + 44 (RCTTurboModuleManager.mm:129)
15  libdispatch.dylib             	       0x180170104 _dispatch_call_block_and_release + 24
16  libdispatch.dylib             	       0x180171978 _dispatch_client_callout + 16
17  libdispatch.dylib             	       0x180179b10 _dispatch_lane_serial_drain + 960
18  libdispatch.dylib             	       0x18017a688 _dispatch_lane_invoke + 388
19  libdispatch.dylib             	       0x180185a84 _dispatch_root_queue_drain_deferred_wlh + 276
20  libdispatch.dylib             	       0x1801850d0 _dispatch_workloop_worker_thread + 448
21  libsystem_pthread.dylib       	       0x1042cb814 _pthread_wqthread + 284
22  libsystem_pthread.dylib       	       0x1042ca5d4 start_wqthread + 8

Expected behavior

ShapeSource.getClusterLeaves() used to worked on old architecture.

Notes / preliminary analysis

No response

Additional links and references

https://github.com/user-attachments/assets/5a8f6a69-0eea-4e19-a3e4-eb01c5ced27b

villemuittari avatar Sep 10 '24 11:09 villemuittari

This is happening to me as well.

https://github.com/user-attachments/assets/3b59ec62-a823-4c1e-8af5-c4349c14bf82

Here is the relevant crash report :

Thread 24 name:
Thread 24 Crashed:
0   libobjc.A.dylib               	0x000000018382605c objc_retain + 8 (:-1)
1   MhMap                         	0x0000000102aab504 -[RNMBXShapeSourceModule getClusterLeaves:featureJSON:number:offset:resolve:reject:] + 72 (RNMBXShapeSourceModule.mm:56)
2   CoreFoundation                	0x000000018b92d814 __invoking___ + 148 (:-1)
3   CoreFoundation                	0x000000018b92c860 -[NSInvocation invoke] + 428 (NSForwarding.m:3411)
4   CoreFoundation                	0x000000018b9a31dc -[NSInvocation invokeWithTarget:] + 64 (NSForwarding.m:3508)
5   MhMap                         	0x0000000102895254 invocation function for block in facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*) + 120 (RCTTurboModule.mm:347)
6   MhMap                         	0x000000010289a978 facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2::operator()() const + 68 (RCTTurboModule.mm:380)
7   MhMap                         	0x000000010289a978 decltype(std::declval<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2&>()()) std::__1::__invoke[abi:ue1700... + 68 (invoke.h:340)
8   MhMap                         	0x000000010289a978 void std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ue170006]<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NS... + 68 (invoke.h:415)
9   MhMap                         	0x000000010289a978 std::__1::__function::__alloc_func<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2, std::__1::allocator<fa... + 68 (function.h:193)
10  MhMap                         	0x000000010289a978 std::__1::__function::__func<facebook::react::ObjCTurboModule::performMethodInvocation(facebook::jsi::Runtime&, bool, char const*, NSInvocation*, NSMutableArray*)::$_2, std::__1::allocator<facebook... + 88 (function.h:364)
11  libdispatch.dylib             	0x000000019383513c _dispatch_call_block_and_release + 32 (init.c:1530)
12  libdispatch.dylib             	0x0000000193836dd4 _dispatch_client_callout + 20 (object.m:576)
13  libdispatch.dylib             	0x000000019383e400 _dispatch_lane_serial_drain + 748 (queue.c:3900)
14  libdispatch.dylib             	0x000000019383ef30 _dispatch_lane_invoke + 380 (queue.c:3991)
15  libdispatch.dylib             	0x0000000193849cb4 _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:6998)
16  libdispatch.dylib             	0x0000000193849528 _dispatch_workloop_worker_thread + 404 (queue.c:6592)
17  libsystem_pthread.dylib       	0x00000001e83e0934 _pthread_wqthread + 288 (pthread.c:2696)
18  libsystem_pthread.dylib       	0x00000001e83dd0cc start_wqthread + 8 (:-1)

gregoryalary avatar Nov 05 '24 15:11 gregoryalary

For information, I did a bit more debugging.

Here are the related line portion in the bridge :

// RNMBXShapeSourceModule.mm:56
RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(double)number offset:(double)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
 [self withShapeSource:viewRef block:^(RNMBXShapeSource *view) {
     [RNMBXShapeSourceViewManager getClusterLeavesWithShapeSource:view featureJSON:featureJSON number:number offset:offset resolver:resolve rejecter:reject];
 } reject:reject methodName:@"getClusterLeaves"];
}

I'm absolutely not competent in native iOS, here is the ChatGPT insight if that helps :

This crash log suggests an issue with memory management, particularly involving objc_retain in the context of the getClusterLeaves method. Here's a breakdown of the potential problem and solutions:

Problem Analysis

Crash Context
The crash happens in the objc_retain function, indicating an issue with object lifecycle management (e.g., an object being released prematurely or being incorrectly retained).

Error Trace
The issue is in the RNMBXShapeSourceModule method, specifically at [RNMBXShapeSourceModule getClusterLeaves:featureJSON:number:offset:resolve:reject:].
The block invocation in [self withShapeSource:viewRef block:] may not properly handle the lifecycle of the RNMBXShapeSource.

Underlying Causes
Null or Invalid Object: The view object passed to the block could be null or improperly managed.
Retain Cycle or Weak Reference: If the view object is weakly referenced but is deallocated before being used, the code might attempt to retain a dangling pointer.
TurboModule Interaction: TurboModule bindings might be passing incorrect or corrupted references to Objective-C objects, causing improper retain/release behavior.

gregoryalary avatar Nov 20 '24 08:11 gregoryalary

same here! temporary disabled new arch (RCT_NEW_ARCH_ENABLED=0 pod install)

aderiushev avatar Nov 21 '24 07:11 aderiushev

Fixed it with a patch-package. Edit RNMBXShapeSourceModule.mm and change

RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(double)number offset:(double)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
 [self withShapeSource:viewRef block:^(RNMBXShapeSource *view) {
     [RNMBXShapeSourceViewManager getClusterLeavesWithShapeSource:view featureJSON:featureJSON number:number offset:offset resolver:resolve rejecter:reject];
 } reject:reject methodName:@"getClusterLeaves"];
}

to

RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(long)number offset:(long)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
 [self withShapeSource:viewRef block:^(RNMBXShapeSource *view) {
     [RNMBXShapeSourceViewManager getClusterLeavesWithShapeSource:view featureJSON:featureJSON number:number offset:offset resolver:resolve rejecter:reject];
 } reject:reject methodName:@"getClusterLeaves"];
}

Then edit RNMBXShapeSourceViewManager.swift And set the number value to 999. Adjust to your need. This is essentially hardcoding it since it's not reading correctly from the JS code. I don't have more energy to figure out why haha.

e.g. change

  @objc public static func getClusterLeaves(
    shapeSource: RNMBXShapeSource,
    featureJSON: String,
    number: uint,
    offset: uint,
    resolver: @escaping RCTPromiseResolveBlock,
    rejecter: @escaping RCTPromiseRejectBlock) -> Void
  {
    shapeSource.getClusterLeaves(featureJSON, number: 999, offset: offset) { result in
        switch result {
        case .success(let features):
          logged("getClusterLeaves", rejecter: rejecter) {
            let featuresJSON : Any = try features.features.toJSON()
            resolver([
              "data": ["type":"FeatureCollection", "features": featuresJSON]
            ])
          }
        case .failure(let error):
          rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
        }
    }
  }

to

  @objc public static func getClusterLeaves(
    shapeSource: RNMBXShapeSource,
    featureJSON: String,
    number: uint,
    offset: uint,
    resolver: @escaping RCTPromiseResolveBlock,
    rejecter: @escaping RCTPromiseRejectBlock) -> Void
  {
    shapeSource.getClusterLeaves(featureJSON, number: number, offset: offset) { result in
        switch result {
        case .success(let features):
          logged("getClusterLeaves", rejecter: rejecter) {
            let featuresJSON : Any = try features.features.toJSON()
            resolver([
              "data": ["type":"FeatureCollection", "features": featuresJSON]
            ])
          }
        case .failure(let error):
          rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
        }
    }
  }

Then create a patch-package so the change persists between npm installs: yarn add patch-package postinstall-postinstall --dev or npm install patch-package postinstall-postinstall --save-dev

then

npx patch-package @rnmapbox/maps

nomadnic avatar Jan 07 '25 20:01 nomadnic

thanks, @nomadnic, here is a patch file to 10.1.33 latest (at the moment) version

diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
index d779a6c..17a8c89 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
@@ -53,7 +53,7 @@ - (void)withShapeSource:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXShapeSou
     } reject:reject methodName:@"getClusterChildren"];
 }
 
-RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(double)number offset:(double)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
+RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(long)number offset:(long)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
 {
  [self withShapeSource:viewRef block:^(RNMBXShapeSource *view) {
      [RNMBXShapeSourceViewManager getClusterLeavesWithShapeSource:view featureJSON:featureJSON number:number offset:offset resolver:resolve rejecter:reject];
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
index 84f0407..f768c98 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
@@ -30,28 +30,28 @@ extension RNMBXShapeSourceViewManager {
       }
   }
 
-  @objc public static func getClusterLeaves(
-    shapeSource: RNMBXShapeSource,
-    featureJSON: String,
-    number: uint,
-    offset: uint,
-    resolver: @escaping RCTPromiseResolveBlock,
-    rejecter: @escaping RCTPromiseRejectBlock) -> Void
-  {
-    shapeSource.getClusterLeaves(featureJSON, number: number, offset: offset) { result in
-        switch result {
-        case .success(let features):
-          logged("getClusterLeaves", rejecter: rejecter) {
-            let featuresJSON : Any = try features.features.toJSON()
-            resolver([
-              "data": ["type":"FeatureCollection", "features": featuresJSON]
-            ])
-          }
-        case .failure(let error):
-          rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
+    @objc public static func getClusterLeaves(
+        shapeSource: RNMBXShapeSource,
+        featureJSON: String,
+        number: uint,
+        offset: uint,
+        resolver: @escaping RCTPromiseResolveBlock,
+        rejecter: @escaping RCTPromiseRejectBlock) -> Void
+      {
+        shapeSource.getClusterLeaves(featureJSON, number: 999, offset: offset) { result in
+            switch result {
+            case .success(let features):
+              logged("getClusterLeaves", rejecter: rejecter) {
+                let featuresJSON : Any = try features.features.toJSON()
+                resolver([
+                  "data": ["type":"FeatureCollection", "features": featuresJSON]
+                ])
+              }
+            case .failure(let error):
+              rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
+            }
         }
-    }
-  }
+      }
 
   @objc public static func getClusterChildren(
     shapeSource: RNMBXShapeSource,

```diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
index d779a6c..17a8c89 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceModule.mm
@@ -53,7 +53,7 @@ - (void)withShapeSource:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXShapeSou
     } reject:reject methodName:@"getClusterChildren"];
 }
 
-RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(double)number offset:(double)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
+RCT_EXPORT_METHOD(getClusterLeaves:(nonnull NSNumber *)viewRef featureJSON:(NSString *)featureJSON  number:(long)number offset:(long)offset resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
 {
  [self withShapeSource:viewRef block:^(RNMBXShapeSource *view) {
      [RNMBXShapeSourceViewManager getClusterLeavesWithShapeSource:view featureJSON:featureJSON number:number offset:offset resolver:resolve rejecter:reject];
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
index 84f0407..f768c98 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXShapeSourceViewManager.swift
@@ -30,28 +30,28 @@ extension RNMBXShapeSourceViewManager {
       }
   }
 
-  @objc public static func getClusterLeaves(
-    shapeSource: RNMBXShapeSource,
-    featureJSON: String,
-    number: uint,
-    offset: uint,
-    resolver: @escaping RCTPromiseResolveBlock,
-    rejecter: @escaping RCTPromiseRejectBlock) -> Void
-  {
-    shapeSource.getClusterLeaves(featureJSON, number: number, offset: offset) { result in
-        switch result {
-        case .success(let features):
-          logged("getClusterLeaves", rejecter: rejecter) {
-            let featuresJSON : Any = try features.features.toJSON()
-            resolver([
-              "data": ["type":"FeatureCollection", "features": featuresJSON]
-            ])
-          }
-        case .failure(let error):
-          rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
+    @objc public static func getClusterLeaves(
+        shapeSource: RNMBXShapeSource,
+        featureJSON: String,
+        number: uint,
+        offset: uint,
+        resolver: @escaping RCTPromiseResolveBlock,
+        rejecter: @escaping RCTPromiseRejectBlock) -> Void
+      {
+        shapeSource.getClusterLeaves(featureJSON, number: 999, offset: offset) { result in
+            switch result {
+            case .success(let features):
+              logged("getClusterLeaves", rejecter: rejecter) {
+                let featuresJSON : Any = try features.features.toJSON()
+                resolver([
+                  "data": ["type":"FeatureCollection", "features": featuresJSON]
+                ])
+              }
+            case .failure(let error):
+              rejecter(error.localizedDescription, "Error.getClusterLeaves", error)
+            }
         }
-    }
-  }
+      }
 
   @objc public static func getClusterChildren(
     shapeSource: RNMBXShapeSource,

aderiushev avatar Jan 15 '25 08:01 aderiushev

The issue is highly relevant when using "react-native": "0.77.2" together with the Magnus-V-main branch.

If you apply the fix as described here, it works correctly.

dontuse avatar May 28 '25 09:05 dontuse

is it still broken in 10.1.39?

krik-chry avatar Jun 03 '25 17:06 krik-chry

is it still broken in 10.1.39?

yes

dontuse avatar Jun 03 '25 23:06 dontuse

Still broken in 10.1.40.

unicron avatar Aug 07 '25 16:08 unicron

I've tried it in 1.2.5 and it worked

'visibleMarkers', [ { properties: { icon: 'example' },
    id: 'a-feature',
    geometry: { type: 'Point', coordinates: [ -74.00597, 40.71427 ] },
    type: 'Feature' },
  { properties: { icon: 'example' },
    id: 'b-feature',
    geometry: { type: 'Point', coordinates: [ -74.001097, 40.71527 ] },
    type: 'Feature' },
  { properties: { icon: 'example' },
    id: 'c-feature',
    geometry: { type: 'Point', coordinates: [ -74.00697, 40.72427 ] },
    type: 'Feature' } ]

mfazekas avatar Oct 18 '25 06:10 mfazekas