Fix map failing to render, dedupe expo-crypto

The home screen passed its FlatList header as `ListHeaderComponent={() =>
(...)}`. VirtualizedList renders a function-valued header prop as
`<HeaderComponent />`, so a fresh arrow function on each render is a fresh
element type: React unmounted the entire header subtree — MapView included —
and mounted a new one. Home re-renders several times on mount (useFetch
loading->data, useUserLocation pending->granted, session resolve), and
recreating the Android GoogleMap surface each time left it grey with the
Google logo and tiles that never finished loading.

Pass the header as an element instead so the type stays stable and MapView
mounts once. The same `() => (...)` pattern in ListEmptyComponent here, and in
rides.tsx and confirm-ride.tsx, cost needless remounts of list chrome and
DriverCards; fixed alongside.

Also pin expo-crypto to ~13.0.2. It was ^57.0.1 — a wrong-major native module
for SDK 51 that nothing in the app imports. npm hoisted it to the top level
and gave expo-auth-session a nested 13.0.2 to satisfy its ~13.0.0 constraint,
leaving two copies of one native module in the tree for autolinking to choose
between.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krikorios
2026-08-24 00:32:34 +03:00
co-authored by Claude Opus 5
parent 59e336c23d
commit 4e0a7cca51
7 changed files with 53 additions and 38 deletions
+9 -1
View File
@@ -193,7 +193,15 @@ EXPO_PUBLIC_GEOAPIFY_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- Go to the [Google Cloud Console](https://console.cloud.google.com/). - Go to the [Google Cloud Console](https://console.cloud.google.com/).
- Create a new project (or use an existing one). - Create a new project (or use an existing one).
- Navigate to the "APIs & Services" section and enable the required APIs (Places API, Directions API). - Navigate to the "APIs & Services" section and enable **all** of the required APIs:
- **Maps SDK for Android** — draws the map itself. Without it Android renders an
empty grey tile area and logcat shows an authorization failure; the app looks
like the map "didn't load."
- **Maps SDK for iOS** — only needed if `components/map.tsx` is switched from
`PROVIDER_DEFAULT` (Apple Maps) to `PROVIDER_GOOGLE`.
- **Places API (New)** — destination search in `components/google-text-input.tsx`.
The legacy Places web service is unavailable to newer Google Cloud projects.
- **Directions API** — route polyline and driver ETAs.
- Go to the "Credentials" tab and click on "Create Credentials." - Go to the "Credentials" tab and click on "Create Credentials."
- Select "API Key." - Select "API Key."
- Copy the generated **API Key** and add it to your `.env` file: - Copy the generated **API Key** and add it to your `.env` file:
+15 -2
View File
@@ -12,11 +12,24 @@ const LOCATION_PERMISSION =
module.exports = ({ config }) => { module.exports = ({ config }) => {
if (!googleMapsApiKey) { if (!googleMapsApiKey) {
console.warn( const message =
"[app.config] EXPO_PUBLIC_GOOGLE_API_KEY is not set — maps will render blank on Android.", "EXPO_PUBLIC_GOOGLE_API_KEY is not set — the Android manifest will have no " +
"Maps key and the map will render blank.";
// `.env` is gitignored, so it is never uploaded to EAS. Unless the key is
// also registered as an EAS environment variable the build silently
// produces a keyless APK, and the blank map only shows up on the device.
// Fail the build here rather than shipping that.
if (process.env.EAS_BUILD) {
throw new Error(
`[app.config] ${message} Register it with \`eas env:create\` (or in the ` +
"EAS dashboard) for this build profile.",
); );
} }
console.warn(`[app.config] ${message}`);
}
return { return {
...config, ...config,
ios: { ios: {
+11 -7
View File
@@ -47,7 +47,6 @@ const Home = () => {
router.push("/(root)/find-ride"); router.push("/(root)/find-ride");
}; };
return ( return (
<SafeAreaView className="bg-general-500"> <SafeAreaView className="bg-general-500">
<FlatList <FlatList
@@ -58,7 +57,7 @@ const Home = () => {
contentContainerStyle={{ contentContainerStyle={{
paddingBottom: 100, paddingBottom: 100,
}} }}
ListEmptyComponent={() => ( ListEmptyComponent={
<View className="flex flex-col items-center justify-center"> <View className="flex flex-col items-center justify-center">
{!loading ? ( {!loading ? (
<> <>
@@ -74,16 +73,21 @@ const Home = () => {
<ActivityIndicator size="small" color="#000" /> <ActivityIndicator size="small" color="#000" />
)} )}
</View> </View>
)} }
ListHeaderComponent={() => ( // Passed as an *element*, not as `() => (...)`. VirtualizedList
// renders a function prop as `<HeaderComponent />`, so a fresh arrow
// function each render is a fresh element type: React unmounts the
// whole header — MapView included — and mounts a new one. Recreating
// the Android map surface on every re-render leaves it grey with the
// Google logo and tiles that never finish loading.
ListHeaderComponent={
<> <>
<View className="flex flex-row items-center justify-between my-5"> <View className="flex flex-row items-center justify-between my-5">
<Text <Text
className="text-base font-JakartaExtraBold" className="text-base font-JakartaExtraBold"
numberOfLines={1} numberOfLines={1}
> >
Welcome{" "} Welcome {user?.name || user?.email} 👋
{user?.name || user?.email} 👋
</Text> </Text>
<View className="flex flex-row items-center gap-x-1"> <View className="flex flex-row items-center gap-x-1">
@@ -140,7 +144,7 @@ const Home = () => {
Recent Rides Recent Rides
</Text> </Text>
</> </>
)} }
/> />
</SafeAreaView> </SafeAreaView>
); );
+4 -6
View File
@@ -23,7 +23,7 @@ const Rides = () => {
contentContainerStyle={{ contentContainerStyle={{
paddingBottom: 100, paddingBottom: 100,
}} }}
ListEmptyComponent={() => ( ListEmptyComponent={
<View className="flex flex-col items-center justify-center"> <View className="flex flex-col items-center justify-center">
{!loading ? ( {!loading ? (
<> <>
@@ -39,12 +39,10 @@ const Rides = () => {
<ActivityIndicator size="small" color="#000" /> <ActivityIndicator size="small" color="#000" />
)} )}
</View> </View>
)} }
ListHeaderComponent={() => ( ListHeaderComponent={
<>
<Text className="text-2xl font-JakartaBold my-5">All rides</Text> <Text className="text-2xl font-JakartaBold my-5">All rides</Text>
</> }
)}
/> />
</SafeAreaView> </SafeAreaView>
); );
+4 -4
View File
@@ -20,13 +20,13 @@ const ConfirmRide = () => {
setSelected={() => setSelectedDriver(item.id)} setSelected={() => setSelectedDriver(item.id)}
/> />
)} )}
ListEmptyComponent={() => ( ListEmptyComponent={
<Text className="text-center text-general-200 font-JakartaMedium mt-10"> <Text className="text-center text-general-200 font-JakartaMedium mt-10">
No drivers available on this route right now.{"\n"}Please try No drivers available on this route right now.{"\n"}Please try
another destination. another destination.
</Text> </Text>
)} }
ListFooterComponent={() => ( ListFooterComponent={
<View className="mx-5 mt-10"> <View className="mx-5 mt-10">
<CustomButton <CustomButton
title="Select Ride" title="Select Ride"
@@ -35,7 +35,7 @@ const ConfirmRide = () => {
className={selectedDriver === null ? "opacity-50" : ""} className={selectedDriver === null ? "opacity-50" : ""}
/> />
</View> </View>
)} }
/> />
</RideLayout> </RideLayout>
); );
+7 -15
View File
@@ -29,7 +29,7 @@
"expo-auth-session": "~5.5.2", "expo-auth-session": "~5.5.2",
"expo-clipboard": "~6.0.3", "expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2", "expo-constants": "~16.0.2",
"expo-crypto": "^57.0.1", "expo-crypto": "~13.0.2",
"expo-font": "~12.0.9", "expo-font": "~12.0.9",
"expo-linking": "^6.3.1", "expo-linking": "^6.3.1",
"expo-location": "^17.0.1", "expo-location": "^17.0.1",
@@ -9337,17 +9337,6 @@
"invariant": "^2.2.4" "invariant": "^2.2.4"
} }
}, },
"node_modules/expo-auth-session/node_modules/expo-crypto": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-13.0.2.tgz",
"integrity": "sha512-7f/IMPYJZkBM21LNEMXGrNo/0uXSVfZTwufUdpNKedJR0fm5fH4DCSN79ZddlV26nF90PuXjK2inIbI6lb0qRA==",
"dependencies": {
"base64-js": "^1.3.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-clipboard": { "node_modules/expo-clipboard": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-6.0.3.tgz", "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-6.0.3.tgz",
@@ -9368,9 +9357,12 @@
} }
}, },
"node_modules/expo-crypto": { "node_modules/expo-crypto": {
"version": "57.0.1", "version": "13.0.2",
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-57.0.1.tgz", "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-13.0.2.tgz",
"integrity": "sha512-xwegXQw3ATgeL1ZuqbSNrGzOeG+zNeh6Z6DSJk825Qpa3TEQQ1kG3ioE1p3g/SNF373BAVz2iBKUTSytlIbBRA==", "integrity": "sha512-7f/IMPYJZkBM21LNEMXGrNo/0uXSVfZTwufUdpNKedJR0fm5fH4DCSN79ZddlV26nF90PuXjK2inIbI6lb0qRA==",
"dependencies": {
"base64-js": "^1.3.0"
},
"peerDependencies": { "peerDependencies": {
"expo": "*" "expo": "*"
} }
+1 -1
View File
@@ -80,7 +80,7 @@
"expo-auth-session": "~5.5.2", "expo-auth-session": "~5.5.2",
"expo-clipboard": "~6.0.3", "expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2", "expo-constants": "~16.0.2",
"expo-crypto": "^57.0.1", "expo-crypto": "~13.0.2",
"expo-font": "~12.0.9", "expo-font": "~12.0.9",
"expo-linking": "^6.3.1", "expo-linking": "^6.3.1",
"expo-location": "^17.0.1", "expo-location": "^17.0.1",