Skip to content

[Android] Non-default system Display size stops native ScrollView/Switch/TextInput from receiving touches (Fabric, Galaxy S23 Ultra) #58360

Closed as not planned

Description

@khemraj-astro

Description

On a Samsung Galaxy S23 Ultra with the system Display size ("Screen zoom") set to anything other than the default, native Android views stop receiving touches from app launch under the New Architecture:

  • ScrollView / FlatList will not scroll
  • Switch will not flip
  • TextInput will not focus
  • native date-picker wheels will not spin
  • the native bottom tab bar does not respond

Meanwhile gesture-handler-based touchables in the same tree work normally. Setting Display size back to default makes everything work again immediately. System font size is irrelevant — only Display size triggers it.

Up front about the limitations of this report (see "What I could not establish" below): I have not been able to produce a reproducer that fires outside this device, so this is a symptom report rather than a diagnosed bug. I am filing it because there appears to be no existing issue describing the touch symptom, and because the mechanism note below may be enough for someone with the affected hardware to confirm or refute quickly.

Environment

  • react-native 0.86.2, New Architecture (Fabric), Hermes
  • Expo SDK 57
  • Samsung Galaxy S23 Ultra, One UI
  • Trigger: Settings → Display → Display size, any non-default value

What I could not establish

  • Not reproducible on a stock Android emulator. adb shell wm density 480 against a physical density of 420 is the same forced-density mechanism as Settings → Display size, but RN's plain ScrollView scrolled normally under it — including on a cold start with the override already applied (4 trials, all scrolled). So either the emulator path differs from One UI's implementation, or the trigger is narrower than "non-default density".
  • Device scope unknown. I only have confirmation on the S23 Ultra. I cannot say whether other Samsung models, other One UI versions, or non-Samsung devices are affected.
  • No version bisect. I do not know whether this worked on an earlier RN release.

I am happy to gather anything specific on the affected device if a maintainer says what would be useful.

Possibly relevant: PixelUtil reads physical density, not configuration density

Offered as a lead, not a diagnosis.

DisplayMetricsHolder.initDisplayMetrics() stores two different things:

val displayMetrics = context.resources.displayMetrics   // honours Display size
windowDisplayMetrics = displayMetrics

val screenDisplayMetrics = DisplayMetrics()
screenDisplayMetrics.setTo(displayMetrics)
wm.defaultDisplay.getRealMetrics(screenDisplayMetrics) // physical; ignores Display size

PixelUtil uses getScreenDisplayMetrics() — the getRealMetrics() one — for every dp↔px conversion (toPixelFromDIP, toPixelFromSP, toDIPFromPixel, getDisplayMetricDensity). A user's Display size setting changes resources.displayMetrics but not getRealMetrics(), so the two diverge exactly when this bug appears.

This also predicts something we observed: density overrides applied at both Activity and Application level had no effect, which is consistent with those overrides changing the configuration/window metrics that PixelUtil never reads.

I want to be clear that I have not verified this is the cause — it is a correlation between the code path and the trigger condition.

Related, though both describe layout/scaling rather than touch:

Workaround, for anyone who lands here

Routing touch through react-native-gesture-handler avoids it entirely — gesture-handler's ScrollView / FlatList on Android, and its pressables in place of RN's touchables. gesture-handler uses its own native-coordinate pipeline, which appears to be unaffected. This is what we shipped; it is a workaround, not a fix.

Activity

  1. react-native-bot commented on Sep 6, 2026

    @react-native-bot
    Collaborator

    Warning

    Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:


    You can read more about about it on our website: How to report a bug.

  2. khemraj-astro commented on Sep 6, 2026

    @khemraj-astro
    Author

    Reproducer (Snack)

    The Snack opens straight from this link (source is inlined in the URL, hence the length):

    https://snack.expo.dev/?platform=android&name=RN%20display-size%20touch%20repro&dependencies=react-native-gesture-handler%40%2A&code=//%20Repro%3A%20non-default%20Android%20%22Display%20size%22%20kills%20native%20touch%20handling.%0A//%0A//%201.%20Open%20on%20an%20affected%20Android%20device%20%28Galaxy%20S23%20Ultra%29%20in%20Expo%20Go.%0A//%202.%20Settings%20%3E%20Display%20%3E%20Display%20size%3A%20leave%20at%20DEFAULT%20-%3E%20everything%20works.%0A//%203.%20Set%20Display%20size%20to%20any%20NON-DEFAULT%20value%2C%20reopen%20-%3E%20the%20ScrollView%20will%0A//%20%20%20%20not%20scroll%2C%20the%20Switch%20will%20not%20flip%2C%20the%20TextInput%20will%20not%20focus.%0A//%20%20%20%20The%20gesture-handler%20ScrollView%20at%20the%20bottom%20keeps%20working.%0Aimport%20React%2C%20%7B%20useState%20%7D%20from%20%27react%27%3B%0Aimport%20%7B%20ScrollView%2C%20Switch%2C%20TextInput%2C%20Text%2C%20View%2C%20StyleSheet%20%7D%20from%20%27react-native%27%3B%0Aimport%20%7B%20ScrollView%20as%20GHScrollView%20%7D%20from%20%27react-native-gesture-handler%27%3B%0A%0Aexport%20default%20function%20App%28%29%20%7B%0A%20%20const%20%5Bon%2C%20setOn%5D%20%3D%20useState%28false%29%3B%0A%20%20const%20%5Btext%2C%20setText%5D%20%3D%20useState%28%27%27%29%3B%0A%20%20return%20%28%0A%20%20%20%20%3CView%20style%3D%7Bstyles.root%7D%3E%0A%20%20%20%20%20%20%3CText%20style%3D%7Bstyles.h%7D%3ERN%20ScrollView%20%28should%20scroll%29%3C/Text%3E%0A%20%20%20%20%20%20%3CScrollView%20style%3D%7Bstyles.box%7D%3E%0A%20%20%20%20%20%20%20%20%7BArray.from%28%7B%20length%3A%2030%20%7D%2C%20%28_%2C%20i%29%20%3D%3E%20%28%0A%20%20%20%20%20%20%20%20%20%20%3CText%20key%3D%7Bi%7D%20style%3D%7Bstyles.row%7D%3ERN%20row%20%7Bi%7D%3C/Text%3E%0A%20%20%20%20%20%20%20%20%29%29%7D%0A%20%20%20%20%20%20%3C/ScrollView%3E%0A%0A%20%20%20%20%20%20%3CText%20style%3D%7Bstyles.h%7D%3ERN%20Switch%20/%20TextInput%3C/Text%3E%0A%20%20%20%20%20%20%3CView%20style%3D%7Bstyles.inline%7D%3E%0A%20%20%20%20%20%20%20%20%3CSwitch%20value%3D%7Bon%7D%20onValueChange%3D%7BsetOn%7D%20/%3E%0A%20%20%20%20%20%20%20%20%3CTextInput%0A%20%20%20%20%20%20%20%20%20%20style%3D%7Bstyles.input%7D%0A%20%20%20%20%20%20%20%20%20%20value%3D%7Btext%7D%0A%20%20%20%20%20%20%20%20%20%20onChangeText%3D%7BsetText%7D%0A%20%20%20%20%20%20%20%20%20%20placeholder%3D%22tap%20to%20focus%22%0A%20%20%20%20%20%20%20%20/%3E%0A%20%20%20%20%20%20%3C/View%3E%0A%0A%20%20%20%20%20%20%3CText%20style%3D%7Bstyles.h%7D%3Egesture-handler%20ScrollView%20%28control%20%E2%80%94%20keeps%20working%29%3C/Text%3E%0A%20%20%20%20%20%20%3CGHScrollView%20style%3D%7Bstyles.box%7D%3E%0A%20%20%20%20%20%20%20%20%7BArray.from%28%7B%20length%3A%2030%20%7D%2C%20%28_%2C%20i%29%20%3D%3E%20%28%0A%20%20%20%20%20%20%20%20%20%20%3CText%20key%3D%7Bi%7D%20style%3D%7Bstyles.row%7D%3EGH%20row%20%7Bi%7D%3C/Text%3E%0A%20%20%20%20%20%20%20%20%29%29%7D%0A%20%20%20%20%20%20%3C/GHScrollView%3E%0A%20%20%20%20%3C/View%3E%0A%20%20%29%3B%0A%7D%0A%0Aconst%20styles%20%3D%20StyleSheet.create%28%7B%0A%20%20root%3A%20%7B%20flex%3A%201%2C%20padding%3A%2012%2C%20paddingTop%3A%2048%20%7D%2C%0A%20%20h%3A%20%7B%20fontWeight%3A%20%27600%27%2C%20marginTop%3A%2012%2C%20marginBottom%3A%204%20%7D%2C%0A%20%20box%3A%20%7B%20flex%3A%201%2C%20borderWidth%3A%201%2C%20borderColor%3A%20%27%23999%27%20%7D%2C%0A%20%20row%3A%20%7B%20padding%3A%2012%20%7D%2C%0A%20%20inline%3A%20%7B%20flexDirection%3A%20%27row%27%2C%20alignItems%3A%20%27center%27%2C%20gap%3A%2012%20%7D%2C%0A%20%20input%3A%20%7B%20flex%3A%201%2C%20borderWidth%3A%201%2C%20borderColor%3A%20%27%23999%27%2C%20padding%3A%208%20%7D%2C%0A%7D%29%3B%0A

    Inlining the source too, since the trigger is a system setting rather than anything unusual in the code:

    import React, { useState } from 'react';
    import { ScrollView, Switch, TextInput, Text, View, StyleSheet } from 'react-native';
    import { ScrollView as GHScrollView } from 'react-native-gesture-handler';
    
    export default function App() {
      const [on, setOn] = useState(false);
      const [text, setText] = useState('');
      return (
        <View style={styles.root}>
          <Text style={styles.h}>RN ScrollView (should scroll)</Text>
          <ScrollView style={styles.box}>
            {Array.from({ length: 30 }, (_, i) => (
              <Text key={i} style={styles.row}>RN row {i}</Text>
            ))}
          </ScrollView>
    
          <Text style={styles.h}>RN Switch / TextInput</Text>
          <View style={styles.inline}>
            <Switch value={on} onValueChange={setOn} />
            <TextInput style={styles.input} value={text} onChangeText={setText} placeholder="tap to focus" />
          </View>
    
          <Text style={styles.h}>gesture-handler ScrollView (control - keeps working)</Text>
          <GHScrollView style={styles.box}>
            {Array.from({ length: 30 }, (_, i) => (
              <Text key={i} style={styles.row}>GH row {i}</Text>
            ))}
          </GHScrollView>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      root: { flex: 1, padding: 12, paddingTop: 48 },
      h: { fontWeight: '600', marginTop: 12, marginBottom: 4 },
      box: { flex: 1, borderWidth: 1, borderColor: '#999' },
      row: { padding: 12 },
      inline: { flexDirection: 'row', alignItems: 'center', gap: 12 },
      input: { flex: 1, borderWidth: 1, borderColor: '#999', padding: 8 },
    });

    Steps

    1. Open on an affected Android device (confirmed only on Galaxy S23 Ultra, One UI).
    2. Display size at default - RN ScrollView scrolls, Switch flips, TextInput focuses.
    3. Settings > Display > Display size set to any non-default value, reopen the app - the RN ScrollView, Switch and TextInput stop responding, while the gesture-handler ScrollView in the same tree keeps scrolling.

    The gesture-handler ScrollView is included deliberately as an in-app control: on an affected device the contrast between the two lists is visible immediately, which should make a yes/no call quick for anyone with the hardware.

    Status of this reproducer - please read

    I want to be straight about what is and isn't verified, rather than have this bounce a second time:

    • The code is just RN's own primitives, so there is nothing app-specific left in it - our own app reproduced the symptom with Expo/react-native-screens/reanimated in the tree, and this strips all of that out.
    • The trigger is a device setting, not code, so reproduction is inherently device-conditional. It cannot be demonstrated on CI or an emulator: as noted in the report, adb shell wm density 480 against a physical 420 did not reproduce it (4 trials, all scrolled).
    • I have not yet re-run this exact Snack on the S23 Ultra - I don't have the device to hand right now. I'm posting it because it's the accepted reproducer format and it lets anyone with an affected device check in under a minute.

    I'll follow up here with the result as soon as it's run on the device: either a confirmation with the One UI build number, or - if this Snack turns out not to fail there - I'll say so plainly, since that would mean the cause is in our stack rather than in React Native and this issue should be closed.

  3. khemraj-astro commented on Sep 7, 2026

    @khemraj-astro
    Author

    Update after on-device testing — one correction to this report, and the lead I offered is refuted

    1. Correction: ScrollView is the wrong headline

    The title and opening of this issue lead with ScrollView, and that turns out to be the one primitive that does work. On-device testing at a non-default Display size:

    • works: plain RN ScrollView — scrolls normally at every Display size tried
    • fails: Switch (won't flip), TextInput (won't focus), the native date-picker wheels (won't spin), FlatList, SectionList, and RefreshControl / pull-to-refresh (onRefresh never fires)

    gesture-handler's equivalents keep working in the same tree, as originally described. Apologies for the misleading framing — I'd have edited the title but I'd rather leave the record visible than quietly rewrite it.

    2. Second affected device — not S23-Ultra-only

    Reproduced on a Samsung SM-G990E (Galaxy S21 FE), Android 16 / One UI:

    $ adb shell wm density
    Physical density: 480
    Override density: 420        # the user's Display size setting
    

    dumpsys display carries both values for the same panel — mBaseDisplayInfo ... density 480 and mOverrideDisplayInfo ... density 420.

    Setting the density back to the panel default made the failing behaviour go away and restoring the override brought it back, so the Display size override is definitely the trigger.

    3. Refuted: the PixelUtil / physical-density lead from the original report

    I suggested above that PixelUtil reading getScreenDisplayMetrics() (i.e. Display.getRealMetrics()) might diverge from the configuration density. That is not what happens. Measured in-app across four override values:

    Override density Dimensions.get('window').scale Dimensions.get('screen').scale
    360 2.25 2.25
    420 (broken) 2.625 2.625
    480 (panel default) 3.0 3.0
    540 3.375 3.375

    getRealMetrics() returns the override, not the base, so React Native sees one consistent density even while the bug is active. Please disregard that part of the original report — and by extension, "make PixelUtil use getWindowDisplayMetrics()" and "call DisplayMetricsHolder.setScreenDisplayMetrics(resources.displayMetrics)" would both be no-ops here. There is nothing to reconcile.

    So whatever mishandles the override is somewhere other than the density value RN exposes to JS.

    4. On the reproducer

    Still not device-confirmed, and I want to be straight about why rather than let it look stronger than it is. My injected-gesture harness turned out to be confounded: the drag started at a fixed pixel offset while the layout rescales with density, so at one density it landed inside a horizontal carousel — and a horizontal scroller swallowing the gesture is indistinguishable from the bug. I briefly inferred a density-value pattern from that and am retracting it.

    A trustworthy harness needs the gesture target expressed in dp and converted per density, a scroll-to-top first, and repeated trials per density. If a maintainer confirms that's worth doing, I'll build it and post the result; equally, if this stays unreproducible outside Samsung hardware I have no objection to it being closed.

  4. khemraj-astro commented on Sep 8, 2026

    @khemraj-astro
    Author

    Resolved — root cause found, and it is not React Native. Closing.

    The touch loss at a non-default Display size was caused by @gorhom/bottom-sheet: a permanently mounted, closed BottomSheet's BottomSheetBackdrop stays pointerEvents: 'auto' as an invisible full-screen view whenever the device's screen height in dp is not an integer. Filed with the full analysis and a Snack at gorhom/react-native-bottom-sheet#2764.

    What we measured on the S23 Ultra at Display size → density 2.625 (screen 2340 px → 891.4285… dp):

    • gorhom's closed-sheet animatedIndex is -0.99999998, not -1: the sheet's initial position is a JS-double Dimensions constant (891.4285714285714) while the container height its index math uses is the float32 layout value (891.4285888671875), and a never-opened sheet is never re-synced. The backdrop gates touchability with an exact animatedIndex <= -1, so it never turns its pointerEvents off.
    • Bisecting the view tree with raw RN probes at several depths: everything outside the navigator worked, everything under it was dead, and every dead touch was delivered to JS with the same target tag, whose owner chain (fiber walk) is BottomSheetBackdropComponent < BottomSheet < … < UpdateAvailableSheet — a closed sheet mounted at the app root. dumpsys activity top shows it as a full-screen, childless ReactViewGroup drawn above the whole navigator.
    • That explains the exact symptom set here: RN's TouchTargetHelper resolves the backdrop as the JS target (so Pressable dies), and a ReactViewGroup with pointerEvents: auto returns true from onTouchEvent, so it also consumes the native touch before Switch/EditText/ScrollView/the picker wheels beneath it (so native controls die). gesture-handler's own hit-testing skips views with alpha < 0.1, which is why every GH control kept working — the observation that sent me looking at RN's density code.
    • Gating the backdrop on our own open/closed state fixed every control listed in this issue on the same device at the same Display size, with React Native untouched.

    So the density values RN exposes were consistent all along (as the previous comment showed), and the Display-size setting was only the trigger for a fractional dp height. Apologies for the noise — leaving the record here in case anyone lands on the same symptom via search: check for a mounted, closed bottom sheet with a backdrop first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions