Skip to content

Say which views the sheet moved, so a mounting crash can be attributed - #5

Open
sergeymild wants to merge 1 commit into
mainfrom
feat/sheet-tree-log
Open

sergeymild wants to merge 1 commit into
mainfrom
feat/sheet-tree-log

Conversation

@sergeymild

Copy link
Copy Markdown
Owner

Why

Two crash families in the MuseScore app land in the mounting layer around a sheet, and neither can be attributed to this library from the report alone:

  • Android — IllegalStateException: addViewAt: failed to insert view [child] into parent [parent] at index N, caused by IndexOutOfBoundsException: index=N count=0. The stack between ViewGroupManager.addView and ViewGroup.addInArray is React Native and framework frames only, so nothing says a sheet was involved. 29 users / 56 events a fortnight (ANDROID-1BXY, plus ANDROID-1D04 arriving through worklets). All six events inspected happen right after returning from Google's SignInHubActivity — a sign-in that starts from a sheet.
  • iOS — EXC_BAD_ACCESS inside RCTMountingManager.performTransaction, at HostFittedSheet.setFittedSheetParams and around dismissSheet (IOS-5AA0, IOS-50QZ, ~490 users). Here the symbols are ours, but the state that led there is not recorded anywhere.

This library legitimately moves views React Native believes it owns, and there are several such places. Which of them (if any) is behind those crashes cannot be answered without saying, at the time it happens, which views were moved.

What this adds

SheetTreeLog on both platforms, recording every one of those moves and naming views the way a crash report names them — the React tag on Android, tag and address on iOS:

Entry What happens
host.addView the previous child is dropped, and React Native never hears about it
host.removeView, host.removeViewAt
sheet.addView an overlay child never reaches mHostView, yet getChildCount() goes on counting it
sheet.removeView, sheet.removeViewAt
sheet.detachHost the host leaves the dialog container on dismiss
inline.attachOverlay, dialog.attachOverlay the overlay is re-parented out of whatever parent React Native gave it
sheet.insertReactSubview, sheet.removeReactSubview, sheet.attachOverlay, sheet.destroy the same on iOS
host.mountChild, host.unmountChild, host.prepareForRecycle what the mounting layer asked for, and where the HostFittedSheet is thrown away and rebuilt

Where the entries go:

  • logcat under Sheet2Tree, and the unified log under the com.sheet2 subsystem;
  • a 32-entry ring per session;
  • on Android, to JS as a sheet2:tree event — addSheetTreeListener() is meant to be wired to breadcrumbs, which is what survives into a native crash report;
  • getSheetTreeLog() reads the ring, the only channel iOS has for now.

Behaviour is unchanged: the same views are moved the same way. The two leftover println("😀 ...") debug lines go with it.

Verified

  • :react-native-sheet2:compileDebugKotlin in the consuming app, including the codegen for the new getTreeLog() on the module spec.
  • On a Galaxy Z Fold (SM-F976B, Android 17) with the app built against this branch, opening and closing a sheet:
D Sheet2Tree: sheet.addView child=1112 index=0 self=1114 hostChildren=none inline=false
D Sheet2Tree: host.addView child=1112 index=0 children=none replaces=-
D Sheet2Tree: sheet.detachHost host=1108 parent=2131362054 parentChildren=1108
D Sheet2Tree: sheet.removeViewAt index=0 self=1108 hostChildren=1106
I ReactNativeJS: '[SHEET2-BRIDGE]' 'sheet.addView child=1112 index=0 self=1114 …'

— so the native → JS chain works end to end.

  • swiftc -parse on the new Swift file. The iOS app build is not part of this checkout; the consuming app builds it next.

🤖 Generated with Claude Code

…ributed

The crashes worth chasing name nothing of ours on Android: "addViewAt: failed to
insert view [child] into parent [parent] at index N", caused by
"IndexOutOfBoundsException: index=N count=0", with a stack made only of React
Native and framework frames. On iOS the symbols are ours
(HostFittedSheet.setFittedSheetParams, dismissSheet inside
RCTMountingManager.performTransaction) but the state that led there is not.

So the library says it itself. SheetTreeLog records every place where a view
React Native believes it owns is moved by us, naming views the way the crash
report names them - by the React tag on Android, by tag and address on iOS:

  host.addView          the previous child is dropped without React Native hearing
  host.removeView / host.removeViewAt
  sheet.addView         an overlay child never reaches mHostView, yet
                        getChildCount goes on counting it
  sheet.removeView / sheet.removeViewAt
  sheet.detachHost      the host leaves the dialog container on dismiss
  inline.attachOverlay / dialog.attachOverlay
                        the overlay is re-parented out of whatever parent React
                        Native gave it
  sheet.insertReactSubview / sheet.removeReactSubview / sheet.attachOverlay /
  sheet.destroy         the same on iOS
  host.mountChild / host.unmountChild / host.prepareForRecycle
                        what the mounting layer asked for, and where the
                        HostFittedSheet is thrown away and rebuilt

Entries go to logcat under Sheet2Tree and to the unified log under com.sheet2,
to a 32-entry ring, and on Android to JS as a "sheet2:tree" event.
addSheetTreeListener turns them into app breadcrumbs - which is what survives
into a native crash report - and getSheetTreeLog() reads the ring, the only
channel iOS has for now.

Nothing changes in behaviour: the same views are moved the same way. The two
leftover println("😀 ...") debug lines go with it.
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

Successfully merging this pull request may close these issues.

1 participant