Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
DazzlingFame opened this issue May 21, 2024 · 4 comments

Comments

@DazzlingFame
Copy link
Contributor

DazzlingFame commented May 21, 2024

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

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

IMG_2327.MP4
@DazzlingFame
Copy link
Contributor Author

DazzlingFame commented May 21, 2024

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

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

Попробовал перейти на либу 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
Copy link
Contributor Author

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

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

@DazzlingFame
Copy link
Contributor Author

DazzlingFame commented May 23, 2024

Мои попытки фиксов 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
Copy link
Contributor

aamagda commented Jul 23, 2024

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

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

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

  • react-native-yamap@4.1.18 → 4.6.0
  • react-native-reanimated@3.6.1 → 3.11.0, react-native-yamap@4.6.0
  • react-native@0.72.12 → 0.73.4
  • react-native@0.73.4, react-native-yamap@4.6.0, react-native-reanimated@3.11.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants