diff --git a/packages/mobile/src/numpad/__stories__/Numpad.stories.tsx b/packages/mobile/src/numpad/__stories__/Numpad.stories.tsx
index 112a95b5d..fbfa15948 100644
--- a/packages/mobile/src/numpad/__stories__/Numpad.stories.tsx
+++ b/packages/mobile/src/numpad/__stories__/Numpad.stories.tsx
@@ -1,5 +1,5 @@
-import { useCallback, useMemo, useState } from 'react';
-import { View } from 'react-native';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { Animated, ScrollView, View } from 'react-native';
import { Banner } from '../../banner/Banner';
import { Button, ButtonGroup } from '../../buttons';
@@ -9,6 +9,7 @@ import { useSafeBottomPadding } from '../../hooks/useSafeBottomPadding';
import { useTheme } from '../../hooks/useTheme';
import { Box, HStack, VStack } from '../../layout';
import { Modal, ModalBody, ModalHeader } from '../../overlays';
+import { Pressable } from '../../system/Pressable';
import { Text } from '../../typography/Text';
import type { NumpadValue } from '../Numpad';
import { DELETE, Numpad, SEPARATOR } from '../Numpad';
@@ -208,15 +209,155 @@ const NumpadExample2 = () => {
);
};
+/**
+ * Stress test to reproduce Android Fabric crash with rapid button presses.
+ * This example adds load to the screen:
+ * - Background state updates (counter incrementing)
+ * - Multiple animated components
+ * - Additional Pressable buttons
+ * - ScrollView with content
+ */
+const NumpadStressTest = () => {
+ const theme = useTheme();
+ const [value, setValue] = useState('');
+ const [pressCount, setPressCount] = useState(0);
+ const [backgroundCounter, setBackgroundCounter] = useState(0);
+
+ // Background state updates to add load
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setBackgroundCounter((prev) => prev + 1);
+ }, 100); // Update every 100ms
+ return () => clearInterval(interval);
+ }, []);
+
+ // Animated values for additional load
+ const pulseAnim = useMemo(() => new Animated.Value(1), []);
+
+ useEffect(() => {
+ const pulse = Animated.loop(
+ Animated.sequence([
+ Animated.timing(pulseAnim, {
+ toValue: 1.1,
+ duration: 500,
+ useNativeDriver: true,
+ }),
+ Animated.timing(pulseAnim, {
+ toValue: 1,
+ duration: 500,
+ useNativeDriver: true,
+ }),
+ ]),
+ );
+ pulse.start();
+ return () => pulse.stop();
+ }, [pulseAnim]);
+
+ const onPress = useCallback((input: NumpadValue) => {
+ setPressCount((prev) => prev + 1);
+ if (input === DELETE) {
+ setValue((prev) => prev.slice(0, -1));
+ } else if (input !== SEPARATOR) {
+ setValue((prev) => prev + input);
+ }
+ }, []);
+
+ const onLongPress = useCallback((input: NumpadValue) => {
+ if (input === DELETE) {
+ setValue('');
+ }
+ }, []);
+
+ return (
+
+ {/* Header with animated element and background counter */}
+
+
+
+ Stress Test
+
+ Press buttons rapidly to test
+
+
+
+
+
+ {pressCount}
+
+
+
+
+
+ Background updates: {backgroundCounter}
+
+
+
+ {/* Additional Pressable buttons to add more Animated.View instances */}
+
+
+ {Array.from({ length: 20 }).map((_, i) => (
+ setPressCount((prev) => prev + 1)}
+ padding={2}
+ >
+ Btn {i + 1}
+
+ ))}
+
+
+
+ {/* Value display */}
+
+ {value || '0'}
+
+
+ {/* Numpad */}
+
+
+
+
+ {/* Footer with more animated elements */}
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+
+ ))}
+
+
+
+ );
+};
+
const NumpadScreen = () => {
return (
-
+
+
+ Rapidly press numpad buttons to test for crashes on Android with New Architecture
+
+
+
+
+
+ {/*
-
+ */}
);
};