volga-volga / react-native-yamap

React Native Yandex Maps | Яндекс Карты | Yandex.MapKit implementation for react native | YandexMaps
158 stars 86 forks source link

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

Open DazzlingFame opened 5 months ago

DazzlingFame commented 5 months ago

Рендерю компоненты 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 commented 5 months ago

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

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

Попробовал перейти на либу 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 commented 5 months ago

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

DazzlingFame commented 5 months ago

Мои попытки фиксов 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
aamagda commented 3 months ago

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

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

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