[FIXED] Instant crash on launch - #1416
Conversation
react-native-gesture-handler 3.x asserts that a GestureDetector owns exactly
one child view when native handlers are attached, in
RNGestureHandlerDetectorView.addView. Reanimated's exiting layout animations
intentionally keep the outgoing view mounted while the incoming one is added,
so an animated direct child briefly gives the detector two children:
java.lang.AssertionError: Cannot have more than one child view when native
gesture handlers are attached to the detector
In the player header the album art is conditional, keyed on the track ID, and
has both entering and exiting animations, so this reproduced on every
next-track press on Android. Wrapping the animated subtree in a stable,
non-collapsible View gives the detector a single child that is never replaced;
the animation swap now happens one level below it. collapsable={false} is
required so Android's view flattening does not remove the wrapper again.
The mini player uses the same pattern with an entering animation and is fixed
the same way. The five other GestureDetector call sites already wrap their
animated content in a stable parent and are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
We found two problems in the font config:
The face map was shifted one step heavy from 300 upward, so 400 resolved to
Figtree-Medium, 600 to Figtree-Bold and 800 to Figtree-Black. Every italic
entry also used spaces rather than hyphens ('Figtree Bold Italic' instead of
'Figtree-BoldItalic'). Android resolves fontFamily by asset filename, so no
italic face ever matched a bundled font and all italics silently fell back to
the system typeface.
The weight scale defined only $4, $6 and $8. A weight token with no entry in
the scale falls through and resolves against a different token scale,
producing values such as fontWeight: 32, which is not a legal weight on Android. On the new architecture this surfaced as a crash on the library selector rather than a silent fallback.
Fills the weight scale to $1-$10 so no token can miss, and corrects the face
map to the standard CSS weight names. Reverts the '$8' -> '$6' workaround in
library-selector, which is no longer needed now that $8 resolves to a real
font file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>From me (pepsisnothanku): I'm really greatful Claude right now! I couldn't have solved these issues without its help. I know AI is a touchy subject right now, but I've been using it to help teach me to code, and I genuinely could not have figured this out without its help. And now it works! I'm so happy!
fetchRawLyrics returned undefined both when the request failed and when the server responded without a Lyrics field. TanStack Query rejects undefined as a query result, logging "Query data cannot be undefined" and leaving the query unresolved, so a track without lyrics produced a console error on every play. Returns null in both cases and narrows the return type to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…al fontWeight. While it was fine for debugging, it crashed on startup in the release build. These commits fix that, letting the release build FINALLY work independently, all features included. It still needs optimizations and has bugs, but those are now added to the comments.
|
|
||
| -keep class com.jellify.BuildConfig { *; } | ||
|
|
||
| # KNOWN GAP: these rules are thin for an app with this many native modules, and R8 |
There was a problem hiding this comment.
Is this change necessary, or just leftover from Claude?
|
|
||
| /** | ||
| * Fetch raw lyrics text for a given track item. | ||
| * |
There was a problem hiding this comment.
I don't think this change is relevant
| } | ||
|
|
||
| const close = () => { | ||
| // TODO: leftover debug logging. These [SR] logs fire on every swipe open/close and |
There was a problem hiding this comment.
Is this relevant to the startup crashes?
| captionAlign = 'center', | ||
| ...cardProps | ||
| }: CardProps) { | ||
| // TODO: remove this call. ItemCard is a list item — it renders once per album / |
There was a problem hiding this comment.
usePerformanceMonitor is a no-op in release mode.
Is this change relevant to fixing crashes?
| import React from 'react' | ||
| import { useTheme, View, YStack, ZStack } from 'tamagui' | ||
| import { useWindowDimensions } from 'react-native' | ||
| // NOTE: this file is the ONLY consumer of react-native-linear-gradient in the app. |
There was a problem hiding this comment.
Is this relevant to the startup crashes?
| const customBlurhash = typeof payload?.blurhash === 'string' ? payload.blurhash : undefined | ||
|
|
||
| return ( | ||
| /* |
There was a problem hiding this comment.
I don't think this is relevant to the startup crashes
| import { ListRenderItemInfo, Platform } from 'react-native' | ||
| import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context' | ||
| // NOTE: react-native-drax@1.1.0 declares `react-native-gesture-handler: ">=2.0.0"` and | ||
| // has no stated support for RNGH 3.x, which this app runs. RNGH 3 was a major rewrite |
There was a problem hiding this comment.
v3 recommended is literally on the react-native-drax website
| @@ -0,0 +1,104 @@ | |||
| # Android debugging notes | |||
There was a problem hiding this comment.
Can we remove this AI generated file?
| import { ReducedMotionConfig, ReduceMotion } from 'react-native-reanimated' | ||
| import { useOtaUpdate } from './src/hooks/ota' | ||
|
|
||
| // NOTE: this hides the LogBox overlay but does NOT make logging free. Console calls |
There was a problem hiding this comment.
What's your source on this?
| const handleRetry = () => setReloader((r) => r + 1) | ||
|
|
||
| return ( | ||
| /* |
There was a problem hiding this comment.
We don't need this here
|
@anultravioletaurora But the actual solution to the problem was with fontWeight; I had to manually declare all the fontWeights. I discovered that the other day. THAT'S what solved the app-crashes-on-opening problem; I just haven't committed it yet. The fontWeight thing made the app actually functional on my device, but it was so buggy it was completely unusable. The rest of the edits that you see on this commit I made targeted that. And I will go through all of these and modify the ai-specific language. Sorry about that; this is really my first time ever contributing to a project on github, so AI is really holding my hand right now. But you asked me to check the relevance of the changes; I'll go through it all and explain what was and wasn't important and why. Sorry for the inconvenience and thanks for bearing with me as I get used to this 😅😊 |
OMG no worries! I appreciate you making the effort to fix it regardless 🙇♀️ @riteshshukla04 got this fixed in #1428 - which has since been merged and released as ...BUT don't be discouraged - and do let me know if there is anything you need from me to create other PRs 😇 |
What is the change
Three independent Android bug fixes, one per commit:
fix: correct Figtree weight tokens and font face mapping— corrects the weight→face map insrc/configs/styling/fonts.tsand fills the weight token scale out to$1–$10.fix: prevent crash on track change from gesture detector child count— wraps the animated subtrees insideGestureDetectorin the player header and mini player with a stable, non-collapsibleView.fix: return null from lyrics fetch when a track has no lyrics—fetchRawLyricsnow returnsnullinstead ofundefined.What does this address
1. Invalid fontWeight values from unresolvable weight tokens
$4,$6and$8. A weight token with no entry falls through and resolves against a different token scale, producing values such asfontWeight: 32, which is not a legal CSS weight. On the new architecture this surfaced as a crash on the library selector (fontWeight={isSelected ? '$8' : 'unset'}) rather than a silent fallback. Filling the scale to$1–$10means no token can miss.Figtree-Medium, 600 toFigtree-Bold, 800 toFigtree-Black. Every italic entry also used spaces rather than hyphens ('Figtree Bold Italic' instead of 'Figtree-BoldItalic'). Since Android resolvesfontFamilyby asset filename, no italic face has ever matched a bundled font — all italics silently fell back to the system typeface.$6and$8, which is the corrected weight rather than a regression.2. Crash on every next-track press (Android)
react-native-gesture-handler3.x asserts that aGestureDetectorowns exactly one child view when native handlers are attached (RNGestureHandlerDetectorView.addView, line 73). Reanimated'sexitinglayout animations intentionally keep the outgoing view mounted while the incoming one is added, so an animated direct child briefly gives the detector two children:java.lang.AssertionError: Cannot have more than one child view when native gesture handlers are attached to the detector at com.swmansion.gesturehandler.react.RNGestureHandlerDetectorView.addView(RNGestureHandlerDetectorView.kt:73) at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:381) ... at com.swmansion.reanimated.NativeProxy.performOperations(Native Method)Player/components/header.tsx, the album art is conditional, keyed on the track ID, and has bothenteringandexitinganimations — so this reproduced on every next-track press. The dispatch originating fromNativeProxy.performOperationsrather than React's commit path is what identifies it as the layout animation rather than an ordinary re-render.Viewgives the detector a single child that is never replaced; the animation swap now happens one level below it.collapsable={false}is required so Android's view flattening doesn't remove the wrapper again. The mini player has the same pattern with anenteringanimation and is fixed identically. The other fiveGestureDetectorcall sites already wrap their animated content in a stable parent and are untouched.assertis gated onClass.desiredAssertionStatus(), so this hard crash may only surface in debuggable builds. The underlying condition — native handlers attaching to a transient child — is a real defect regardless of whether the assertion fires.Query data cannot be undefinedon tracks without lyricsfetchRawLyricsreturnedundefinedboth on request failure and when the server responded without aLyricsfield. TanStack Query rejectsundefinedas a result, loggingQuery data cannot be undefined. Affected query key: ["TRACK_LYRICS", ...]and leaving the query unresolved — so any track without lyrics produced a console error on every play.Testing
bun tsc— 0 errorsbun lint— 0 errorsbun format:check— cleanbun test— 122/122 passing$6/$8weight tokens render correctly with tokens restored.Issue number / link
Issue: #1415
Link: #1415
This person had the same issue also on their Pixel 11. My issue was on a Pixel 9; this PR could solve both issues:
Issue: #1413
Link: #1413
Tag reviewers
@anultravioletaurora