Skip to main content

Overview

PARKINMX uses Expo SDK ~54.0.27 with file-based routing via Expo Router. The app is configured to run on iOS, Android, and Web with platform-specific optimizations.
Expo SDK: ~54.0.27Expo Router: ~6.0.17React Native: 0.81.5

App Configuration

The main Expo configuration is in app.json:
app.json
{
  "expo": {
    "name": "parkinmx",
    "slug": "parkinmx",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/images/icon.png",
    "scheme": "parkinmx",
    "userInterfaceStyle": "automatic",
    "newArchEnabled": true,
    
    "ios": {
      "supportsTablet": true
    },
    
    "android": {
      "adaptiveIcon": {
        "backgroundColor": "#E6F4FE",
        "foregroundImage": "./assets/images/android-icon-foreground.png",
        "backgroundImage": "./assets/images/android-icon-background.png",
        "monochromeImage": "./assets/images/android-icon-monochrome.png"
      },
      "edgeToEdgeEnabled": true,
      "predictiveBackGestureEnabled": false,
      "permissions": [
        "android.permission.USE_BIOMETRIC",
        "android.permission.USE_FINGERPRINT"
      ]
    },
    
    "web": {
      "output": "static",
      "favicon": "./assets/images/favicon.png"
    },
    
    "plugins": [
      "expo-router",
      [
        "expo-splash-screen",
        {
          "image": "./assets/images/splash-icon.png",
          "imageWidth": 200,
          "resizeMode": "contain",
          "backgroundColor": "#ffffff",
          "dark": {
            "backgroundColor": "#000000"
          }
        }
      ],
      "expo-web-browser",
      "expo-secure-store",
      "expo-local-authentication",
      "expo-barcode-scanner"
    ],
    
    "experiments": {
      "typedRoutes": true,
      "reactCompiler": true
    },
    
    "extra": {
      "router": {},
      "eas": {
        "projectId": "932388e1-77d0-406c-8f32-ea382c964a89"
      }
    },
    
    "owner": "alejandromv03"
  }
}

Configuration Breakdown

Basic App Info

name
string
App display name: parkinmx
slug
string
URL-friendly identifier used in Expo Go and EAS
version
string
Current app version: 1.0.0
orientation
string
Locked to portrait mode for consistent UX
scheme
string
Deep linking scheme: parkinmx://Enables opening the app via custom URLs:
Linking.openURL('parkinmx://reservar');
userInterfaceStyle
string
automatic - Supports both light and dark mode based on system settings

React Native New Architecture

"newArchEnabled": true
The new architecture enables:
  • JSI (JavaScript Interface): Direct JS-to-native communication without the bridge
  • Fabric: New rendering system for better performance
  • TurboModules: Lazy-loaded native modules
  • Concurrent Rendering: Better UI responsiveness

Platform-Specific Configuration

iOS Configuration

app.json
"ios": {
  "supportsTablet": true
}

iPhone Support

Optimized for all iPhone models (SE to Pro Max)

iPad Support

Full tablet support with responsive layouts
Additional iOS Setup (if building locally):
"ios": {
  "supportsTablet": true,
  "bundleIdentifier": "com.yourcompany.parkinmx",
  "buildNumber": "1.0.0",
  "infoPlist": {
    "NSCameraUsageDescription": "ParkInMX needs camera access to scan QR codes for parking check-in.",
    "NSFaceIDUsageDescription": "ParkInMX uses Face ID to securely authenticate you.",
    "NSLocationWhenInUseUsageDescription": "ParkInMX needs your location to show nearby parking lots."
  }
}

Android Configuration

app.json
"android": {
  "adaptiveIcon": {
    "backgroundColor": "#E6F4FE",
    "foregroundImage": "./assets/images/android-icon-foreground.png",
    "backgroundImage": "./assets/images/android-icon-background.png",
    "monochromeImage": "./assets/images/android-icon-monochrome.png"
  },
  "edgeToEdgeEnabled": true,
  "predictiveBackGestureEnabled": false,
  "permissions": [
    "android.permission.USE_BIOMETRIC",
    "android.permission.USE_FINGERPRINT"
  ]
}

Adaptive Icons

The main icon graphic displayed on top of the background.Requirements:
  • Size: 108x108dp (432x432px @4x)
  • Format: PNG with transparency
  • Safe zone: 66dp diameter circle in center
Either a solid color (#E6F4FE) or an image.Used as the backdrop for the foreground icon.
Single-color version for themed icons (Android 13+).Allows the system to apply user-selected colors to the icon.

Android Features

edgeToEdgeEnabled
boolean
true - Content draws behind system bars for immersive UI
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const insets = useSafeAreaInsets();
// Apply insets.top, insets.bottom to avoid system UI overlap
predictiveBackGestureEnabled
boolean
false - Disables Android 14+ predictive back animationsSet to false if your app has custom navigation transitions that conflict with the system gesture.
permissions
array
Requested Android permissions:
  • USE_BIOMETRIC - Fingerprint/face unlock
  • USE_FINGERPRINT - Legacy fingerprint API (pre-Android 9)
Additional Android Setup:
"android": {
  "package": "com.yourcompany.parkinmx",
  "versionCode": 1,
  "permissions": [
    "android.permission.USE_BIOMETRIC",
    "android.permission.USE_FINGERPRINT",
    "android.permission.CAMERA",
    "android.permission.INTERNET"
  ]
}

Web Configuration

app.json
"web": {
  "output": "static",
  "favicon": "./assets/images/favicon.png"
}
output
string
static - Generates static HTML files for hostingAlternative: "server" for SSR (not commonly used for Expo apps)
Web Build Command:
npx expo export:web
Outputs to dist/ directory, ready for deployment to:
  • Vercel
  • Netlify
  • GitHub Pages
  • Any static hosting service

Expo Plugins

Active Plugins

Enables file-based routing with support for:
  • Stack navigation
  • Tabs navigation
  • Modal routes
  • Dynamic routes
  • Deep linking
Entry Point: expo-router/entry (set in package.json main field)
Configures the app splash screen shown during initial load.
[
  "expo-splash-screen",
  {
    "image": "./assets/images/splash-icon.png",
    "imageWidth": 200,
    "resizeMode": "contain",
    "backgroundColor": "#ffffff",
    "dark": {
      "backgroundColor": "#000000"
    }
  }
]
Usage in Code:
import * as SplashScreen from 'expo-splash-screen';

SplashScreen.preventAutoHideAsync(); // Keep visible

// Later, after loading:
await SplashScreen.hideAsync();
Opens URLs in an in-app browser (instead of external browser).
import * as WebBrowser from 'expo-web-browser';

await WebBrowser.openBrowserAsync('https://parkinmx.com/terms');
Benefits:
  • OAuth flows (keep user in app)
  • Better UX for external links
  • Automatic credential autofill
Encrypted key-value storage for sensitive data.iOS: Keychain ServicesAndroid: EncryptedSharedPreferences
hooks/useSeguridad.ts
import * as SecureStore from 'expo-secure-store';

// Store PIN securely
await SecureStore.setItemAsync('userPIN', hashedPIN);

// Retrieve
const storedPIN = await SecureStore.getItemAsync('userPIN');
Biometric authentication (Face ID, Touch ID, fingerprint).
hooks/useSeguridad.ts
import * as LocalAuthentication from 'expo-local-authentication';

// Check if biometrics available
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();

// Authenticate
const result = await LocalAuthentication.authenticateAsync({
  promptMessage: 'Authenticate to access ParkInMX',
  fallbackLabel: 'Use PIN instead'
});

if (result.success) {
  // Proceed to app
}
QR code and barcode scanning.
components/ScannerModal.tsx
import { Camera } from 'expo-camera';

const [hasPermission, setHasPermission] = useState(null);

useEffect(() => {
  (async () => {
    const { status } = await Camera.requestCameraPermissionsAsync();
    setHasPermission(status === 'granted');
  })();
}, []);

<Camera
  onBarCodeScanned={({ data }) => {
    console.log('Scanned:', data);
    // Verify reservation code
  }}
  style={StyleSheet.absoluteFillObject}
/>

Other Key Expo Modules

These don’t require explicit plugin configuration but are used throughout the app:

expo-notifications

Push notifications (local & remote)

expo-image-picker

Select photos from gallery or camera

expo-camera

Direct camera access for QR scanning

expo-haptics

Tactile feedback (vibrations)

expo-blur

Native blur effects for iOS/Android

expo-print

PDF generation and printing

expo-sharing

Native share sheet

expo-constants

Access app.json and device info

Experimental Features

app.json
"experiments": {
  "typedRoutes": true,
  "reactCompiler": true
}

Typed Routes

Enables TypeScript autocomplete and type checking for Expo Router navigation:
import { useRouter } from 'expo-router';

const router = useRouter();

// ✅ TypeScript knows these routes exist
router.push('/reservar');
router.push('/perfil');
router.push({ pathname: '/EditReservationScreen', params: { id: '123' } });

// ❌ TypeScript error: route doesn't exist
router.push('/nonexistent');

React Compiler

Experimental - May have bugs or performance issues
Automatically optimizes React components:
  • Memoizes components without React.memo
  • Optimizes hooks without useMemo/useCallback
  • Reduces unnecessary re-renders
How it works: The React Compiler analyzes your components at build time and inserts optimizations automatically.

Package.json Scripts

package.json
{
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "lint": "expo lint",
    "reset-project": "node ./scripts/reset-project.js"
  }
}
# Start development server
npm start

# Start with specific platform
npm run android
npm run ios
npm run web

# Clear cache and restart
npm start -- --clear

EAS Configuration

app.json
"extra": {
  "eas": {
    "projectId": "932388e1-77d0-406c-8f32-ea382c964a89"
  }
}
EAS (Expo Application Services) provides:
  • Cloud builds (no Xcode/Android Studio required)
  • OTA updates
  • App submission to stores

EAS Build Profiles

Create eas.json in project root:
eas.json
{
  "cli": {
    "version": ">= 5.2.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "simulator": true
      }
    },
    "preview": {
      "distribution": "internal",
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "autoIncrement": true,
      "env": {
        "EXPO_PUBLIC_ENV": "production"
      }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "[email protected]",
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDEF1234"
      },
      "android": {
        "serviceAccountKeyPath": "./google-service-account.json",
        "track": "production"
      }
    }
  }
}
1

Install EAS CLI

npm install -g eas-cli
2

Login

eas login
3

Configure Project

eas build:configure
4

Build

# Production build
eas build --platform all --profile production

# Preview APK
eas build --platform android --profile preview

Deep Linking

app.json
"scheme": "parkinmx"
Enables opening the app via custom URLs: Universal Links (iOS) and App Links (Android) configuration:
app.json
"ios": {
  "associatedDomains": [
    "applinks:parkinmx.com"
  ]
},
"android": {
  "intentFilters": [
    {
      "action": "VIEW",
      "autoVerify": true,
      "data": [
        {
          "scheme": "https",
          "host": "parkinmx.com",
          "pathPrefix": "/"
        }
      ],
      "category": [
        "BROWSABLE",
        "DEFAULT"
      ]
    }
  ]
}
Usage:
import * as Linking from 'expo-linking';

// Open app from external URL
const url = await Linking.getInitialURL();
if (url) {
  const { path, queryParams } = Linking.parse(url);
  // Navigate based on path
}

// Listen for incoming links
Linking.addEventListener('url', ({ url }) => {
  const { path } = Linking.parse(url);
  router.push(path);
});

Asset Management

assets/
└── images/
    ├── icon.png                    # 1024x1024 app icon
    ├── splash-icon.png             # Splash screen logo
    ├── favicon.png                 # Web favicon
    ├── android-icon-foreground.png # 432x432 adaptive icon fg
    ├── android-icon-background.png # 432x432 adaptive icon bg
    └── android-icon-monochrome.png # 432x432 themed icon
Use expo-asset to preload critical images:
import { Asset } from 'expo-asset';

await Asset.loadAsync([
  require('./assets/images/icon.png'),
  require('./assets/images/splash-icon.png')
]);

Environment Variables

Expo supports environment variables with the EXPO_PUBLIC_ prefix:
.env
EXPO_PUBLIC_API_URL=https://api.parkinmx.com
EXPO_PUBLIC_FIREBASE_API_KEY=your_key_here
EXPO_PUBLIC_GEMINI_API_KEY=your_gemini_key
Access in code:
import Constants from 'expo-constants';

const apiUrl = process.env.EXPO_PUBLIC_API_URL;
// or
const firebaseKey = Constants.expoConfig?.extra?.firebaseApiKey;
EXPO_PUBLIC_ variables are bundled into the app and visible to users. Never store secrets here!

Performance Tips

Hermes is enabled by default in Expo SDK 54+Benefits:
  • Faster app startup
  • Reduced memory usage
  • Smaller app size
Use expo-image instead of react-native Image:
import { Image } from 'expo-image';

<Image
  source={{ uri: photoURL }}
  placeholder={blurhash}
  contentFit="cover"
  transition={200}
/>
Advantages:
  • Native image caching
  • Better performance
  • Blur hash placeholders
Animations run on native thread, never drop frames:
import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated';

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ scale: withSpring(scale.value) }]
}));

<Animated.View style={animatedStyle} />
Use dynamic imports for heavy screens:
import { lazy, Suspense } from 'react';

const HeavyScreen = lazy(() => import('./HeavyScreen'));

<Suspense fallback={<LoadingSpinner />}>
  <HeavyScreen />
</Suspense>

Troubleshooting

# Clear Metro bundler cache
npm start -- --clear

# Clear node_modules and reinstall
rm -rf node_modules
npm install

# Clear iOS build
cd ios && rm -rf build Pods Podfile.lock && cd ..
npx pod-install

# Clear Android build
cd android && ./gradlew clean && cd ..
After modifying plugins in app.json:
# Prebuild native projects
npx expo prebuild --clean
This regenerates ios/ and android/ folders with updated plugin configurations.
# Kill all Metro processes
killall -9 node

# Restart with clear cache
npm start -- --reset-cache

Next Steps

Architecture Overview

Learn about the overall app architecture and tech stack

Build docs developers (and LLMs) love