Skip to content

Commit 66c454a

Browse files
NateIsernclaude
andcommitted
Wire react-native-keyboard-controller at root layout
Mount `KeyboardProvider` (from react-native-keyboard-controller) as the outermost provider so every screen gets a consistent cross-platform keyboard lifecycle. On Android this fixes the Places map search pill losing focus one tick after tapping: the library replaces React Native's unreliable keyboard bookkeeping with a native module that stays in sync through `BottomSheet.snapToIndex` animations. With the provider in place, the `requestAnimationFrame` workaround around the map's `handleSearchFocus` is no longer needed — the snap runs synchronously inside `onFocus` and focus is preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 886fa63 commit 66c454a

3 files changed

Lines changed: 23 additions & 22 deletions

File tree

app/_layout.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { vars } from "nativewind";
2424
import * as Linking from "expo-linking";
2525
import { GestureHandlerRootView } from "react-native-gesture-handler";
2626
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
27+
import { KeyboardProvider } from "react-native-keyboard-controller";
2728
import { SafeAreaProvider } from "react-native-safe-area-context";
2829
import {
2930
BloomThemeProvider,
@@ -190,20 +191,22 @@ export default function RootLayout() {
190191

191192
return (
192193
<GestureHandlerRootView style={{ flex: 1 }}>
193-
<SafeAreaProvider>
194-
<BloomThemeProvider
195-
mode={mode}
196-
colorPreset="faircoin"
197-
onModeChange={handleModeChange}
198-
>
199-
<BottomSheetModalProvider>
200-
<AppContent
201-
key={language}
202-
ready={fontsLoaded && themeReady && languageReady}
203-
/>
204-
</BottomSheetModalProvider>
205-
</BloomThemeProvider>
206-
</SafeAreaProvider>
194+
<KeyboardProvider>
195+
<SafeAreaProvider>
196+
<BloomThemeProvider
197+
mode={mode}
198+
colorPreset="faircoin"
199+
onModeChange={handleModeChange}
200+
>
201+
<BottomSheetModalProvider>
202+
<AppContent
203+
key={language}
204+
ready={fontsLoaded && themeReady && languageReady}
205+
/>
206+
</BottomSheetModalProvider>
207+
</BloomThemeProvider>
208+
</SafeAreaProvider>
209+
</KeyboardProvider>
207210
</GestureHandlerRootView>
208211
);
209212
}

app/map.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,15 +344,12 @@ export default function MapScreen() {
344344
const [userLocation, setUserLocation] = useState<UserCoords | null>(null);
345345

346346
// Tapping the search field expands the sheet to its top snap so the
347-
// filtered list fills the screen. We defer the snap one frame via
348-
// `requestAnimationFrame` — snapping synchronously inside the
349-
// `onFocus` handler fights with React Native's own focus bookkeeping
350-
// on Android and the TextInput loses focus a beat later. Queuing the
351-
// snap for the next frame lets focus settle first.
347+
// filtered list fills the screen. `KeyboardProvider` at the root
348+
// layout keeps the keyboard in sync with the focused TextInput across
349+
// bottom-sheet snap animations, so we can snap synchronously without
350+
// Android losing focus a beat later.
352351
const handleSearchFocus = useCallback(() => {
353-
requestAnimationFrame(() => {
354-
sheetRef.current?.snapToIndex(SHEET_INDEX_SEARCH);
355-
});
352+
sheetRef.current?.snapToIndex(SHEET_INDEX_SEARCH);
356353
}, []);
357354

358355
const handleSearchBlur = useCallback(() => {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"react-dom": "19.2.0",
6363
"react-native": "^0.83.4",
6464
"react-native-gesture-handler": "^2.31.1",
65+
"react-native-keyboard-controller": "1.20.7",
6566
"react-native-qrcode-svg": "^6.3.21",
6667
"react-native-reanimated": "4.2.1",
6768
"react-native-safe-area-context": "~5.6.2",

0 commit comments

Comments
 (0)