react-native-yamap icon indicating copy to clipboard operation
react-native-yamap copied to clipboard

Краш - Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException

Open DazzlingFame opened this issue 1 year ago • 4 comments

Рендерю компоненты Marker детьми внутри компонента YaMap, при изменении позиции на карте изменяю набор детей, вплоть до полного изменения без пересечений После очередного изменения позиции карты маркеры "прилипают" к краю карты, затем происходит краш карты

Пример текста ошибки (могут меняться числа children и indicesToRemove)

Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException: Trying to remove a view index above child count 0 view tag: 3523
 detail: View tag:3523 View Type:class ru.vvdev.yamap.view.YamapView
  children(0): [
 ],
  indicesToRemove(1): [
0,
 ],
  tagsToDelete(1): [
68847,
 ]

Окружение "react-native": "0.70.14", "react-native-yamap": "4.1.18", Android 13

StackTrace:

 Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException: Trying to remove a view index above child count 0 view tag: 3523
 detail: View tag:3523 View Type:class ru.vvdev.yamap.view.YamapView
  children(0): [
 ],
  indicesToRemove(1): [
0,
 ],
  tagsToDelete(1): [
68847,
 ]

       at com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:437)
       at com.swmansion.reanimated.layoutReanimation.ReanimatedNativeHierarchyManager.manageChildren(ReanimatedNativeHierarchyManager.java:300)
       at com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:217)
       at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:915)
       at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:1026)
       at com.facebook.react.uimanager.UIViewOperationQueue.-$$Nest$mflushPendingBatches()
       at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:1086)
       at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29)
       at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175)
       at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1648)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1659)
       at android.view.Choreographer.doCallbacks(Choreographer.java:1129)
       at android.view.Choreographer.doFrame(Choreographer.java:1045)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1622)
       at android.os.Handler.handleCallback(Handler.java:958)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loopOnce(Looper.java:230)
       at android.os.Looper.loop(Looper.java:319)
       at android.app.ActivityThread.main(ActivityThread.java:8893)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)

Видео воспроизведения:

https://github.com/volga-volga/react-native-yamap/assets/15292833/c8926343-26bb-4466-8f84-6f901db0b46c

DazzlingFame avatar May 21 '24 11:05 DazzlingFame

Мои попытки фиксов:

Обновление либы

Попробовал перейти на либу react-native-yamap-plus, в которой используется нативный map-kit версии 4.6.1. Проблема не пропала

Фикс в removeChild в YamapView

Стектрейс указывает на remove a view в ru.vvdev.yamap.view.YamapView

внутри YamapView видим метод removeChild, внутри которого уже реализованы проверки на наличие mapObject, те тут проблем возникать не должно

if (getChildAt(index) instanceof ReactMapObject) {
          final ReactMapObject child = (ReactMapObject) getChildAt(index);
          if (child == null) return;
          final MapObject mapObject = child.getMapObject();
          if (mapObject == null || !mapObject.isValid()) return;
          getMap().getMapObjects().remove(mapObject);
}

Однако на всякий случай пробуем отловить краш в try catch

    public void removeChild(int index) {
      try {
        if (getChildAt(index) instanceof ReactMapObject) {
          final ReactMapObject child = (ReactMapObject) getChildAt(index);
          if (child == null) return;
          final MapObject mapObject = child.getMapObject();
          if (mapObject == null || !mapObject.isValid()) return;

          Log.d("!!!removeChild success", String.valueOf(index));
          getMap().getMapObjects().remove(mapObject);
        }
      } catch (Exception e) {
        Log.d("!!!removeChild error", e.getMessage());
      }
    }

Но это не помогает, краши все равно воспроизводятся, лог removeChild error никогда не выводится

Фикс в removeViewAt в YamapViewManager

Тк падение не отловилось через try catch в YamapView, смотрю на removeViewAt, который последовательно вызывает

parent.removeChild(index);
super.removeViewAt(parent, index);

пробую проверить гипотезу, что parent.removeChild отрабатывает корректно, а super.removeViewAt падает

Изменение порядка вызовов

Обычно super методы вызываются в самом начале, пробуем изменить порядок вызовов

    public void removeViewAt(YamapView parent, int index) {
      super.removeViewAt(parent, index);
      parent.removeChild(index);
    }

Это не помогает

Опциональный вызов super.removeViewAt

попробовал возвращать булеан из removeChild, чтобы понимать снаружи, что удаление прошло корректно

    public boolean removeChild(int index) {
      try {
        if (getChildAt(index) instanceof ReactMapObject) {
          final ReactMapObject child = (ReactMapObject) getChildAt(index);
          if (child == null) return false;
          final MapObject mapObject = child.getMapObject();
          if (mapObject == null || !mapObject.isValid()) return false;

          getMap().getMapObjects().remove(mapObject);
          return true;
        }
      } catch (Exception e) {
        Log.d("!!!removeChild error", e.getMessage());
        return false;
      }
      return false;
    }

и далее вызывать super.removeViewAt только если removeChild вернул true, те удаление прошло успешно

    public void removeViewAt(YamapView parent, int index) {
      if (parent.removeChild(index)) {
          super.removeViewAt(parent, index);
      }
    }

Это тоже не помогло, краши остались

Дополнительная проверка на наличие чайлда через super

Перед вызовом super.removeViewAt делаю дополнительную проверку на наличие чайлда по индексу, по которому предполагается удаление

    public void removeViewAt(YamapView parent, int index) {
      if (parent.removeChild(index)) {
        if (super.getChildAt(parent, index) != null) {
          super.removeViewAt(parent, index);
        }
      }
    }

это тоже не помогает Аналогично не помогает проверка на размер списка детей

    public void removeViewAt(YamapView parent, int index) {
      if (parent.removeChild(index)) {
        if (super.getChildCount(parent) > index) {
          super.removeViewAt(parent, index);
        }
      }
    }

DazzlingFame avatar May 21 '24 11:05 DazzlingFame

Гипотезы и вопросы:

  • Нативный код падает, но приложение не крашится. Падает в каком-то потоке, который не критичен для работы JS?
  • Карта практически не падает на сильных устройствах, но зато стабильно падает, когда воспроизвожу на загруженном тормозящем эмуляторе. Проблема в каком-то race condition, который усугубляется на медленных девайсах?

DazzlingFame avatar May 21 '24 12:05 DazzlingFame

Мои попытки фиксов V2:

removeViewAt в YamapViewManager в try catch

Решил попробовать полностью обернуть removeViewAt в try catch, чтобы понять, правильно ли локализовал место краша

    public void removeViewAt(YamapView parent, int index) {
      try {
        parent.removeChild(index);
        super.removeViewAt(parent, index);
        Log.d("!!!removeViewAt", "success");
      } catch (Exception e) {
        Log.d("!!!removeViewAt", e.getMessage());
      }
    }

Это не принесло результатов, лог в catch ни разу не отработал, хотя краш проявился

image

DazzlingFame avatar May 23 '24 13:05 DazzlingFame

По стектрейсу падает в этом месте react-native-reanimated.

:green_circle: Пробовали вариант с включением enableLayoutAnimations(true) из react-native-reanimated, проблема пропадает, видим динамику снижения крашей на пользователях. :yellow_circle: Но при этом крайне редко бывает что не отрисовываются новые пины до тех пор пока снова не подвигаешь карту

:red_circle: Что еще пробовали, но не помогло

aamagda avatar Jul 23 '24 14:07 aamagda