Skip to content

Mobile Application

The mobile application is built with React Native and Expo, featuring authentication, navigation, and dark mode support.

  • React Native with Expo (~54.0)
  • React Navigation (native stack)
  • NativeWind (Tailwind CSS for React Native)
  • Zustand (state management)
  • TanStack Query (data fetching)
  • Better Auth (authentication)
  • Jest + React Native Testing Library (testing)
  • EAS Build (builds and deployment)
  • Node.js 24.10.0
  • pnpm 10.5.2
  • Expo CLI (installed automatically)
  • iOS Simulator (for iOS development on macOS)
  • Android Studio or Android Emulator (for Android development)

From the repository root:

Terminal window
pnpm install

Copy the environment file:

Terminal window
cp apps/mobile/.env.example apps/mobile/.env

Edit .env and configure:

Terminal window
EXPO_PUBLIC_API_URL=http://localhost:3000

From the repository root:

Terminal window
pnpm dev:mobile

Or from the mobile folder:

Terminal window
cd apps/mobile
pnpm dev

This will start the Expo development server. You can then:

  • Press i to open iOS simulator
  • Press a to open Android emulator
  • Scan the QR code with Expo Go app on your physical device
apps/mobile/
├── src/
│ ├── app/ # App-level components
│ │ ├── app-providers.tsx
│ │ └── app-container.tsx
│ ├── features/ # Feature modules
│ │ ├── auth/
│ │ │ ├── screens/
│ │ │ │ ├── login-screen.tsx
│ │ │ │ ├── register-screen.tsx
│ │ │ │ └── reset-password-screen.tsx
│ │ │ ├── components/
│ │ │ │ ├── login-form.tsx
│ │ │ │ └── forgot-password-modal.tsx
│ │ │ └── schemas/
│ │ │ ├── auth.schema.ts
│ │ │ ├── forgot-password.schema.ts
│ │ │ └── reset-password.schema.ts
│ │ ├── home/
│ │ │ ├── home-screen.tsx
│ │ │ └── components/
│ │ │ ├── posts-list.tsx
│ │ │ └── post-card.tsx
│ │ └── profile/
│ │ └── profile-screen.tsx
│ ├── navigation/ # Navigation configuration
│ │ ├── types.ts
│ │ ├── root-navigator.tsx
│ │ └── use-linking.ts
│ ├── components/ # Shared components
│ │ ├── atoms/ # Atomic components
│ │ │ ├── button.tsx
│ │ │ ├── input.tsx
│ │ │ └── password-input.tsx
│ │ ├── organisms/ # Complex components
│ │ │ └── slide-up-modal.tsx
│ │ └── index.ts
│ ├── common/ # Common utilities
│ │ └── hooks/
│ │ ├── use-auth-initialization.ts
│ │ └── use-theme.ts
│ ├── api/ # API queries
│ │ └── queries/
│ │ └── posts-queries.ts
│ ├── store/ # Zustand stores
│ │ ├── auth-store.ts
│ │ └── theme-store.ts
│ ├── theme/ # Theme configuration
│ │ └── colors.ts
│ ├── i18n/ # Internationalization
│ │ ├── index.ts
│ │ └── config.ts
│ └── lib/ # External library configs
│ ├── auth-client.ts
│ └── query-client.tsx
├── config/ # App configuration
│ └── env.config.ts
├── App.tsx # App entry point
├── app.config.ts # Expo configuration
├── eas.json # EAS Build configuration
└── package.json
Terminal window
# iOS
pnpm ios
# Android
pnpm android
# Web (for quick testing)
pnpm web
Terminal window
pnpm typecheck
Terminal window
pnpm lint
Terminal window
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch

The mobile app uses Better Auth with the same configuration as the web apps. Users can:

  • Sign up with email and password
  • Sign in with email and password
  • Sign out
  • Reset password via email

Authentication state is managed globally with Zustand and persists across app restarts.

  • Scheme: lonestone://reset-password?token=... (configurable via EXPO_PUBLIC_SCHEME in apps/mobile/.env and CLIENTS_MOBILE_SCHEME in apps/api/.env)
  • Important: Expo Go does not support custom schemes; you need a dev build or an installed app with this scheme to test
Terminal window
# 1. Generate native iOS/Android projects with your custom scheme
pnpm expo prebuild --clean --no-install
# 2. Install the dev build on iOS simulator (required!)
pnpm expo run:ios
# 3. Test the deep link (replace "lonestone" with your project's scheme)
xcrun simctl openurl booted "lonestone://reset-password?token=dd8MiuMnEjAJy4JFH4MbBFgq"
# Alternative: Via uri-scheme utility
npx uri-scheme open "lonestone://reset-password?token=YOUR_TOKEN" --ios

The current flow uses a simple deeplink. To switch to Universal Links (iOS) / App Links (Android):

  1. Prepare the apple-app-site-association / assetlinks.json files on your domain
  2. Use the utility script pnpm create:universallinks to generate the base configuration
  3. Update the iOS/Android config (entitlements, manifest) and align the API to send the universal URL

The app supports automatic dark mode based on system preferences. Users can also toggle dark mode manually using the useTheme hook:

import { useTheme } from '@/common/hooks/use-theme'
function MyComponent() {
const { colorScheme, toggleColorScheme, isDark } = useTheme()
return (
<Button onPress={toggleColorScheme}>
Toggle {isDark ? 'Light' : 'Dark'} Mode
</Button>
)
}

For features that don’t work with Expo Go:

Terminal window
# Build for iOS
eas build --profile development --platform ios
# Build for Android
eas build --profile development --platform android

Install the development build on your device, then start the dev server with pnpm dev.

For internal testing:

Terminal window
# iOS and Android
eas build --profile preview --platform all
# iOS only
eas build --profile preview --platform ios
# Android only (APK)
eas build --profile preview --platform android

For app store submission:

Terminal window
eas build --profile production --platform all

The mobile app has a dedicated GitHub Actions workflow (.github/workflows/mobile-ci.yml) that:

  • Runs linting and type checking
  • Executes tests
  • Builds preview versions on pull requests
  • Builds production versions on main branch pushes

Clear the cache:

Terminal window
pnpm dev --clear
  1. Restart the Metro bundler after changing .env
  2. Ensure variables are prefixed with EXPO_PUBLIC_
  3. Check expo-env.d.ts for type declarations
  • On iOS simulator: use http://localhost:3000
  • On Android emulator: use http://10.0.2.2:3000
  • On physical device: use your computer’s IP address (e.g., http://192.168.1.100:3000)